P3.NET

Updating the UI On an Arbitrary Thread

(Originally published: 5 March 2007)

It is very common to have to perform work on a secondary thread.  This allows us to provide a more interactive UI to our users while still allowing us to do lengthy work.  Unfortunately this is not as easy as it first appears.  We will discuss the problem, the standard v1.x solution and the v2 solution to the problem.

Why Do We Need It

Windows has always had the cardinal rule that a UI element can only be interacted with on the thread that created it.  This is because internally Windows still uses a message queue to communicate with UI elements.  The message queue is per-thread.  It is created on the thread that created the element.  This ensures that all messages are serialized to the UI and simplifies our work. 

However it is often necessary to do work on secondary threads and still update the UI.  In order for this to work we have to actually send the request to the UI’s thread and then have it pushed onto the message queue.  There are a couple of different ways to do this.  We’ll look at the most common method in v1.x and v2.  .NET v3.x introduces the concept of synchronization contexts which can be used to execute code on specific threads.  Synchronization contexts are a lengthy topic that will not be covered in this article.

v1.x Solution – Invoke and InvokeRequired

Every UI element (a control, here on out) has a InvokeRequired property.  This property can be called on any thread and indicates whether the calling thread is the thread that created the control.  If it is then the property is false otherwise it is true.  When working with controls in a multithreaded environment you must check this property.  If it is true then you must marshal the request to the UI thread. 

You marshal the request to the UI thread using the Invoke or BeginInvoke/EndInvoke methods.  These methods can be called on any thread.  In each case they accept a delegate, switch to the UI thread and then call the delegate.

if (myControl.InvokeRequired) 

   myControl.Invoke(…);
else 
   …

To make things easier we generally wrap the above code in a method and then create a delegate with the same signature.  This allows us to use one function for two different purposes.  The first “version” is called in all cases and determines whether an invoke is required.  The second “version” actually does the work on the UI thread.  This is a nice way to hide the details from your clients.

public delegate void UpdateProgressDelegate ( int progress );

public void UpdateProgress ( int progress )
{
   //If not on UI thread
   if (myProgress.InvokeRequired)
      myProgress.Invoke(new UpdateProgressDelegate(UpdateProgress), new object[] { progress });
   else
   {
      //Do work here – called on UI thread
      myProgress.Value = progress;
   };
}

As far as your users are concerned this is a clean interface.  They simply call UpdateProgress.  Internally if an invocation is required to get the call on the UI thread then it does so.  However it calls the same method again.  The second time it is called it will be done on the UI thread.  The biggest downside to this code is that: a) you have to write it, and b) you have to define a delegate.  Here is how we might call it.

public void Run ( )
{
   Thread t = new Thread(new ThreadStart(DoWork)); 
   t.IsBackground = true
   t.Start();
}

public void UpdateProgress ( int progress ) 
{ myProgress.Value = progress; } 

private void DoWork ( ) 

   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { 
      Thread.Sleep(50); 
      UpdateProgress(nIdx);
   }; 

v2 Solution – BackgroundWorker Component

Since this is such a common situation in v2 Microsoft has added the BackgroundWorker component (BWC).  This component’s sole purpose in life is to do work on a secondary thread and then update the UI.  The component requires that you identify the function to run.  It will then run the function on a thread pool thread.  Once it is complete the component will raise the RunWorkerCompleted event.

public void Run ( )
{
   BackgroundWorker bwc = new BackgroundWorker(); 
   bwc.DoWork += DoWork; 
   bwc.RunWorkerCompleted += OnCompleted; 
   bwc.RunWorkerAsync();    //Run it
}

private void OnCompleted ( object sender, RunWorkerCompletedEventArgs e ) 
{ MessageBox.Show(“Done”); } 

private void DoWork ( object sender, DoWorkEventArgs e ) 

   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { Thread.Sleep(50); }; 
}

Notice a few things about the code.  Firstly it is smaller as we don’t have to worry about the thread management.  Secondly notice we do not have to worry about the UI thread.  All events exposed by BWC are raised on the UI thread automatically.  Note that you can drop BWC onto your form or control because it shows up in the designer.  Personally I prefer to create it on the fly.

There is a problem with the above code.  It does not actually update the progress while it is running.  In order to raise progress events we need to tell BWC we will be raising progress notifications.  We also need to hook up another function to handle the event.

public void Run ( )
{
   BackgroundWorker bwc = new BackgroundWorker(); 
   bwc.DoWork += DoWork; 
   bwc.RunWorkerCompleted += OnCompleted; 
   bwc.WorkerReportsProgress = true
   bwc.ProgressChanged += OnProgressChanged;

   bwc.RunWorkerAsync();    //Run it
}

private void OnProgressChanged ( object sender, ProgressChangedEventArgs e ) 
{ myProgress.Value = e.ProgressPercentage; }

private void OnCompleted ( object sender, RunWorkerCompletedEventArgs e ) 
{ MessageBox.Show(“Done”); } 

private void DoWork ( object sender, DoWorkEventArgs e ) 

   BackgroundWorker bwc = sender as BackgroundWorker; 
   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { 
      Thread.Sleep(50); 
      bwc.ReportProgress(nIdx); 
   };
}

The changes are in bold.  Basically the UpdateProgress of earlier changes to OnProgressChanged.  Again, it is called on the UI thread.  We have to report progress using BWC.ReportProgress.

One final feature of BWC that was not in the original code we wrote is cancellation support.  It is often nice to allow users to cancel lengthy tasks.  BWC supports this automatically.  All you have to do is tell it that you will allow cancellation and then periodically check for a cancellation request.  For algorithms that loop you generally check at the beginning of each loop.  The more frequently you check the cancellation flag (CancellationPending) the faster your user will see the process be cancelled.

public void Run ( )
{
   BackgroundWorker bwc = new BackgroundWorker(); 
   bwc.DoWork += DoWork; 
   bwc.RunWorkerCompleted += OnCompleted; 
   bwc.WorkerReportsProgress = true
   bwc.ProgressChanged += OnProgressChanged;
   bwc.WorkerSupportsCancellation = true
   bwc.RunWorkerAsync();    //Run it
}

private void btnCancel_Click ( object sender, EventArgs e ) 
{ bwc.CancelAsync(); }

private void OnProgressChanged ( object sender, ProgressChangedEventArgs e ) 
{ myProgress.Value = e.ProgressPercentage; }

private void OnCompleted ( object sender, RunWorkerCompletedEventArgs e ) 
{
   if (e.Cancelled) 
      MessageBox.Show(
“Cancelled”); 
   else 
      MessageBox.Show(“Done”);

private void DoWork ( object sender, DoWorkEventArgs e ) 

   BackgroundWorker bwc = sender as BackgroundWorker; 
   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { 
      if (bwc.CancellationPending)
      {
         e.Cancel = 
true;
         
return
      };

      Thread.Sleep(50); 
      bwc.ReportProgress(nIdx); 
   };
}

Here we’ve set the property notifying BWC that we support cancellation and we check for cancellation during each iteration of the loop.  We also modified the completion handler to detect whether the operation completed or was cancelled.  The only hard part here is actually hooking a control (such as a button) up to cancel the work.  If you use the designer to drop the BWC on your form then you can use the field that is created to access the BWC.  In the above code it was a local variable so you would have to somehow store the local variable somewhere where the cancel button’s click handler could get to it.  I leave that for you to figure out.

Why Not Do It Automatically

Some people ask why, if it is so common, Windows does not do this automatically or why the framework developers didn’t do it, or why they shouldn’t do it themeselves.  The answer is performance.  As common as it might be most UI interaction still occurs on the appropriate thread.  It is not really worth the performance hit to always handle the situation.  This is a common theme throughout the framework.  Another problem with threading messages is the order.  In general if you send two messages (one right after the other) then you expect the first message to be processed first and then the second message.  With asynchronous calls this might not be true.  Therefore more effort must be put into ensuring that the ordering of messages is not important. 

Most classes in the framework are not thread safe.  The reasoning is that it adds complexity and degrades performance.  A good multithreaded appliation does not allow just any object to be accessed across threads.  This would be a bad design.  Instead only a select few objects are shared and it is these objects that need to be thread safe, not the entire application.

Until Windows sheds its message queue behavior I’m afraid we are stuck with this limitation.  Although I believe WPF has somewhat remedied the situation…

Comments

  1. This My first thread on this or any other online community for that matter.
    I just wanted to introduce to you myself about myself, I am a technology teacher and i am very honored to share my knowledge with the rest of the people
    I have a wife and 3 dogs. I am also a jets fan and I love hunting
    See this as my introduction. I hope I can be of some help to the forum, PM me with any questions.

    dvervfverv rav

    installing microsoft windows 7 ultimate