Search code examples
wpfbackgroundworkerdispatch

BackgroundWorker and Dispatcher.Invoke


Like many applications, I want to update my status text when the application is doing long computation. I've read articles about Dispatcher and BackgroundWorker. I know I definitely need to make sure the the updates happen in the UI thread. My first try was:

MyView.UpdateStatus( "Please wait" );
LongComputation();
MyView.UpdateStatus( "Ready" );

This does not work because (I think) the LongComputation prevents the status being updated.

So I tried to do this:

BackgroundWorker worker = new BackgroundWorker();
MyView.UpdateStatus( "Please wait");
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
    LongComputation();
}
worker.RunWorkerAsync();
MyView.UpdateStatus( "Ready" );

I was hopping that the extra thread will give the UpdateStatus a chance to update status text. It does not work either. One of the reason is that the result of the LongComputation is displayed in a Windows Form. As soon as I put the LongComputation inside the BackgroundWorker, the result doesn't show up.

So I tried a third time by using the flowing code:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
    Dispatcher.Invoke(((Action)(() => Status.Text = args.Argument as string)));
};

worker.RunWorkerAsync(newStatus);

I was hoping that putting the update in a another thread would work. But it didn't.

What can I do to make sure that the status reflects the correct program state?


Solution

  • BackgroundWorker uses the RunWorkerCompleted and the ReportProgress events to communicate back to the main thread. RunWorkerCompleted should do what you need to do as it will get executed on the UI thread as soon as the background work is complete.

            BackgroundWorker worker = new BackgroundWorker();
    
            worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                LongComputation();
            };
    
            // RunWorkerCompleted will fire on the UI thread when the background process is complete
            worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
            {
                if (args.Error != null)
                {
                    // an exception occurred on the background process, do some error handling
                }
    
                MyView.UpdateStatus("Ready");
            };
    
            MyView.UpdateStatus("Please wait");
            worker.RunWorkerAsync();
    

    Additionally, you can use the RunWorkerCompleted to marshall results back to the main thread using the Result property of the DoWorkerEventArgs.

            worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                args.Result = LongComputation();
            };
    
            worker.rep
    
            // RunWorkerCompleted will fire on the UI thread when the background process is complete
            worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
            {
                if (args.Error != null)
                {
                    // an exception occurred on the background process, do some error handling
                }
    
                var result = args.Result;
    
                // do something on the UI with your result
    
                MyView.UpdateStatus("Ready");
            };
    

    Finally, you can use the ReportProgress event you update your UI at logical steps in your background process:

            worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                FirstHalfComputation();
    
                // you can report a percentage back to the UI thread, or you can send 
                // an object payload back
                int completedPercentage = 50;
                object state = new SomeObject();
                worker.ReportProgress(completedPercentage , state); 
    
                SecondHalfComputation();
            };
    
            worker.WorkerReportsProgress = true;    // this is important, defaults to false
            worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
            {
                int completedPercentage = args.ProgressPercentage;
                SomeObject state = args.UserState as SomeObject
    
                // update a progress bar or do whatever makes sense
                progressBar1.Step = completedPercentage;
                progressBar1.PerformStep();
            };