Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run code without block main thread

I need to generate n random strings and this process may take a while and block the main thread UI. For avoid this and let user use the programm while the process is running I decided to use a backGroundWorker. But it didn't work well and the main thread still is blocked. In my DoWork event I have something like this:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
           // using Invoke because I change value of some controls, e.g,   ListView, labels and a progressbar
            this.Invoke((Action)delegate
            {
                for (int i = 0; i < nSteps; ++i)
                {
                    string s = getnStr();
                    // ...
                    int progress = (int)(100.0 / nSteps * (i + 1));
                    backgroundWorker1.ReportProgress(progress);
                }
            });
        }

Even although I call ReportProgress() inside the loop, the progressbar only changes its value when the loop is done.

Why is this happening and how can I fix this?

like image 992
Jack Avatar asked Aug 22 '15 20:08

Jack


3 Answers

Invoke... That's where you get it all wrong. You're only creating a background worker just so you invoke the code back to the main thread (that's what Invoke does).

You should actually either use invoke only for the controls access, either modify the controls only in the ReportProgress handler and then make sure you call the ReportProgress method every time when needed.

EDIT: Clarification: your problem with the invoke is that you're invoking the UI thread for the entire workload that the background worker was supposed to do.

like image 80
Mihai Caracostea Avatar answered Nov 02 '22 09:11

Mihai Caracostea


The other answer explained the behavior you are seeing. Please read it first. Here is how to fix this:

BackgroundWorker is obsolete because we have Task and await now. It automates a lot. Your code probably should look like this:

            //In the button click handler:
            for (int i = 0; i < nSteps; ++i)
            {
                await DoSomeWorkAsync();
                int progress = (int)(100.0 / nSteps * (i + 1));
                SetProgressBarValue(progress);
            }

That's really all. You need to ensure that DoSomeWorkAsync does not block. It must return a Task.

like image 3
usr Avatar answered Nov 02 '22 09:11

usr


Besides reporting progress ReportProgress() method can be used as a generic asynchronous event dispatcher (e.g to update text in UI controls):

 for (int i = 0; i < nSteps; ++i)
 {
   string s = getnStr();

   // Update text
   backgroundWorker1.ReportProgress(0, "My text..");    

   // Update progress
   int progress = (int)(100.0 / nSteps * (i + 1));
   backgroundWorker1.ReportProgress(progress);
 }

ProgressChanged event handler would look something like this:

void backgroundWorker1_ProgressChanged(object sender,  ProgressChangedEventArgs e)
{
     if (e.UserState != null)
     {
           // Handle text update
           label1.Text = (string)e.UserState;
           return;
     } 

     progressBar1.Value = e.ProgressPercentage;   
}
like image 1
alexm Avatar answered Nov 02 '22 08:11

alexm