(C#) BackgroundWorker() ProgressChanged not working

2020-02-15 c# wpf multithreading backgroundworker

I have a WPF application that consist of two threads simulating an enterprise producting and selling items in 52 weeks (only one transaction is allowed per week). I need to use a background worker as well so that I can display the data in a listview. As of right now, my UI freezes when clicking on simulate but I can see that the output is still working in the debugging terminal. I have tried everything that I can think of and to be honest, I have had the help of my teacher and even he couldn't find a working solution.


  1. What is freezing my UI when I call Simulate() ?
  2. When my code is different and my UI isn't freezing, my listview never updates because it seems that DataProgress() doesn't work — e.UserStart is never iterating.

Simulate button calls :

private void Simulate(object sender, RoutedEventArgs e)
{
    // Declare BackgroundWorker
    Data = new ObservableCollection<Operations>();
    worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
    worker.RunWorkerAsync(52);

    worker.DoWork += ShowData;
    worker.ProgressChanged += DataProgress;
    worker.RunWorkerCompleted += DataToDB;

    Production = new Production(qtyProduction, timeExecProd);
    Sales = new Sales(qtySales, timeExecSales);

    Thread prod = new Thread(Production.Product);
    prod.Start();

    Thread.Sleep(100);

    Thread sales = new Thread(Sales.Sell);
    sales.Start();
}

DoWork : ShowData() :

Console.WriteLine("Simulation started | Initial stock : 500");

Production = new Production(qtyProduction, timeExecProd);
Sales = new Sales(qtySales, timeExecSales);

while (Factory.Week < max) // max = 52 
{
    if (worker.CancellationPending) // also this isn't reacting to worker.CancelAsync();
        e.Cancel = true;

   // My teacher tried to call my threads from here, but it breaks the purpose of having 
   // two threads as he was just calling 52 times two functions back to back and therefore
   // wasn't "randomizing" the transactions.

    int progressPercentage = Convert.ToInt32(((double)(Factory.Week) / max) * 100);
    (sender as BackgroundWorker).ReportProgress(progressPercentage, Factory.Week);
}

ProgressChanged : DataProgress() :

if (e.UserState != null) // While using debugger, it looks like this is called over & over
{
    Data.Add(new Operations() 
    {
        id = rnd.Next(1,999),
        name = Factory.name,
        qtyStock = Factory.Stock,
        averageStock = Factory.AverageStock,
        week = Factory.Week
    });
    listview.ItemsSource = Data;
}

RunWorkerCompleted : DataToDB() :

    // Outputs "Work done" for now.

In case you want to know what happens when I call my threads, it looks like this :

Sell() :

while (Factory.Week <= 52)
{
    lock (obj)
    {
        // some math function callings¸
        Factory.Week++;
    }
    Thread.Sleep(timeExecSales);
}

Should I use a third thread just for updating my listview? I don't see how as I need it to be synced with my static variables. This is my first project for learning multithreading... I'm kind of clueless and flabbergasted that even my teacher can't help.

Answers

On the one hand, there isnt enough context in the code posted to get a full picture to answer your questions accurately. We can, however, deduce what is going wrong just from the code you have posted.

First, lets try to answer your two questions. We can likely infer the following:

This code here:

if (e.UserState != null) 
{
    Data.Add(new Operations() 
    {
        id = rnd.Next(1,999),
        name = Factory.name,
        qtyStock = Factory.Stock,
        averageStock = Factory.AverageStock,
        week = Factory.Week
    });
    listview.ItemsSource = Data;
}

You are using a Windows Forms background thread object to try and update a WPF GUI object which should only be done on the main GUI thread. There is also the obvious no-no of never updating GUI objects from non-UI threads. Using BackgroundWorker also has its own issues with threading (foreground/background), contexts and execution, as it relies on the Dispatcher and SynchronizationContexts to get the job done.

Then there is the curiosity of setting the binding over and over in this line:

listview.ItemsSource = Data;

Let's put a pin in that for a moment...

There is, as the other commenter pointer out already, no exit strategy in your while loop:

while (Factory.Week < max) // max = 52 
{
    if (worker.CancellationPending) // also this isn't reacting to worker.CancelAsync();
        e.Cancel = true;

   // My teacher tried to call my threads from here, but it breaks the purpose of having 
   // two threads as he was just calling 52 times two functions back to back and therefore
   // wasn't "randomizing" the transactions.

    int progressPercentage = Convert.ToInt32(((double)(Factory.Week) / max) * 100);
    (sender as BackgroundWorker).ReportProgress(progressPercentage, Factory.Week);
}

But thats not the bigger problem... in addition to the misuse/misunderstanding of when/how many/how to use threading, there doesnt seem to be any kind of thread synchronization of any kind. There is no way to predict or track thread execution of lifetime in this way.

At this point the question is technically more or less answered, but I feel like this will just leave you more frustrated and no better off than you started. So maybe a quick crash course in basic design might help straighten out this mess, something your teacher should have done.

Assuming you are pursuing software development, and since you have chosen WPF here as your "breadboard" so to speak, you will likely come across terms such as MVC (model view controller) or MVVM (model view view-model). You will also likely come across design principles such as SOLID, separation of concerns, and grouping things into services.

Your code here is a perfect example of why all of these frameworks and principles exist. Lets look at some of the problems you have encountered and how to fix them:

  1. You have threading code (logic and services - controller [loosely speaking]) mixed in with presentation code (listview update - view) and collection update (your observable collection - model). Thats one reason (of many) you are having such a difficult time coding, fixing and maintaining the problem at hand. To clean it up, separate it out (separation of concerns). You might even move each operation into its own class with an interface/API to that class (service/ micro-service).

  2. Not everything needs to be solved with threads. But for now, lets learn to crawl, then walk before we run. Before you start learning about async/await or the TPL (task parallel library) lets go old school. Get a good book... something even from 20 years ago is find... go old school, and learn how to use the ThreadPool and kernel synchronization objects such as mutexes, events, etc and how to signal between threads. Once you master that, then learn about TPL and async/await.

  3. Dont cross the streams. Dont mix WinForms, WPF and I even saw Console.WriteLine.

  4. Learn about Data Binding, and in particular how it works in WPF. ObservableCollection is your friend, bind your ItemsSource to it once, then update the ObservableCollection and leave the GUI object alone.

Hopefully this will help you straighten out the code and get things running.

Good luck!

Related