Updating Controls From Worker Threads

I thought this was an issue that has been hashed over enough times, but the other day I was asked this question and I’m still amazed that many folks are unfamiliar with this concept.

In WinForms applications, sometimes its desirable to execute a long running method in a worker thread. For instance, you may be retreiving data over a web service that has a heavy load and usually takes several seconds to execute.. When the work has completed, it’s typical that you would want to update the user interface: be that a grid, a tree view control, or some other container that displays the results of your call to the long running process.  Sounds simple enough right?

Before you get in a hurry, consider one thing.  Data created on one thread is owned by that thread as long as it is local data.  This can be overcome though by marshaling the data across threads.  This is a function of Thread Local Storage implemented through managed code. Virtual address space of a process is shared across all threads.  While the data of a thread is unique, it can be “shared” or copied but you need to tell the application to copy the data to a the UI thread before updating the UI with the data.  This can be done using Control.Invoke().

Take a look at what happens when you don’t use this method.  Createa  new Windows Forms project, add a TreeView control (tvResults) and two buttons.( btnNormalExe and btnThreadExe ).  Add the following methods on the form:

public void WorkerThread() {
    Cursor.Current = Cursors.WaitCursor;
    // Simulate Long Process (5 seconds)
    Thread.Sleep( new TimeSpan( 0,0,0,5,0 ) );

    UpdateResults();
    Cursor.Current = Cursors.Arrow;
}

public void UpdateResults() {
  // Update UI
  tvResults.Nodes.Add(
    String.Format( "UI Updated on Thread ID {0} at {1}", 
       AppDomain.GetCurrentThreadId().ToString(),
       DateTime.Now.ToString() ) );
}

For btnNormalExe add the following code:

 

private void btnNormalExe_Click(object sender, System.EventArgs e) {
  WorkerThread();
}

For btnThreadExe add the following code:

private void btnThreadExe_Click(object sender, System.EventArgs e) {
  Thread t = new Thread( new ThreadStart( WorkerThread) );
  t.Start();
}

Now run the form and click on the normal execution button first. Once the method completes, click on the thread execution button.  You’ll receive an exception when the threaded execution completes:

An unhandled exception of type 'System.InvalidOperationException' 
occurred in system.windows.forms.dll
Additional information: The action being performed on this control 
is being called from the wrong thread. You must marshal to the 
correct thread using Control.Invoke or Control.BeginInvoke to 
perform this action.

Just as the exception says, we are going to modify our call to make sure we get back on the UI’s thread before updating it.  In this way, the form’s thread owns the data, not the worker thread. Modify the UpdateResults() method as follows:

public void WorkerThread() {
  Cursor.Current = Cursors.WaitCursor;
  // Simulate Long Process (5 seconds)
  Thread.Sleep( new TimeSpan( 0,0,0,5,0 ) ); 

  //UpdateResults(); 
  this.Invoke( new TreeViewUpdater( UpdateResults ) ); 
  Cursor.Current = Cursors.Arrow;
}

Notice that when you execute both the normal execution button and the threaded execution button the tree view control updates with the same thread ID. This is because the call to control.Invoke puts the call back on the form’s thread.

I will cover this in more depth in the weeks to come, to shed more light on what’s happening in the background.

Be Sociable, Share!

    One Thought on “Updating Controls From Worker Threads

    1. Thanks to Tobin’s post on a topic I’ve been dealing with, I was able to potentially solve my problem….

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Post Navigation