Cross Thread Communication in Windows Forms: Part I


Today we will see with the help of an example how to perform cross thread communication/passing data between threads with the help of an example; we will work out a sample wherein we marshal a method call from Non-UI thread to a Windows UI thread.

For those who are not familiar with the Windows Forms architecture, the Windows UI works on a dedicated thread of its own and for heavy operation (time consuming, which could block/hang the UI) it is often advisable to perform such tasks on a separate thread, otherwise one often sees a "cross thread communication exception".

Here today we will see three different ways to perform this cross thread communication:

  1. Backgroundworker Class
  2. SynchronizationContext Class
  3. Invoke/BeginInvoke methods available from the UI controls.

So let us start with the help of a small sample; using your devenv select a Windows Forms template.

When you are ready with your Windows Forms, go the form's designer and drag and add a RichTextBox control, dock it to the top.
Next do the same with the progress bar, drag it and dock it the top, also reduce the height a bit.

Folllowing that, add three buttons arranged horizontally below the progress bar. Give them a name, I gave each name as:
  • InvokeMarshal
  • BackgroundWorker
  • SyncMarshal

Now you your designer should look something similar to this:

CTCWinFor1.gif

We will start with a BackgroundWorker implementation; for that .NET 2.0 comes with a class called BackgroundWorker.

This class is very easy to use and handles the marshalling of the cross thread communication on its own internally, so there is not much for the developer creating threads using this approach to worry about. There are 3 events which are of prime importance while using this class, namely:

// Summary:
// Occurs when System.ComponentModel.BackgroundWorker.RunWorkerAsync() is called.
public event DoWorkEventHandler DoWork;

//
// Summary:
//    Occurs when System.ComponentModel.BackgroundWorker.ReportProgress(System.Int32)

//    is called.
public event ProgressChangedEventHandler ProgressChanged;

//
// Summary:
//     Occurs when the background operation has completed, has been canceled, or
//     has raised an exception.
public event RunWorkerCompletedEventHandler RunWorkerCompleted;

Of these events I will be using only two:

  1. DoWork to perform the long running task in a separate thread.

  2. RunWorkerCompleted when the task is over, to notify the UI thread.

Let us implement the code for the BackgroundWorker class; in the constructor of the Form, let us initialize the BackgroundWorker class:

if (m_worker == null)

    m_worker = new BackgroundWorker();
    m_worker.DoWork += new DoWorkEventHandler(m_worker_DoWork);
    m_worker.RunWorkerCompleted += new                 RunWorkerCompletedEventHandler(m_worker_RunWorkerCompleted);
}

Next let us implement functionalities to be performed in the event handlers:

private void m_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Result != null)
    {
        if (Convert.ToString(e.Result) == "BackgroundWorker")
        {
            richTextBox1.AppendText("Call marshalled in BackgroundWorker RunWorkerCompleted event.");
        }
        progressBar1.Visible = false;
        EnableButtons(true);
    }
}

private void m_worker_DoWork(object sender, DoWorkEventArgs e)
{
    Thread.Sleep(5000);
    e.Result = e.Argument;
}

In the DoWork event handler we have put the thread to sleep for 5 sec and once elapsed, we set the result in the next line, which invokes the RunWorkerCompleted event in turn, which internally marshals back the event handler method as seen on top to UI thread and sets the log to richtextbox, hides the progress bar and enable the buttons as seen on the designer.

Next we need to invoke the backgroundWorker class instance too, for that we create the event handler for the BackgroundWorker button in UI:

private void btnWorker_Click(object sender, EventArgs e)
{
    if (m_worker != null && !m_worker.IsBusy)
    {
        EnableButtons(false);
        progressBar1.Visible = true;
        m_worker.RunWorkerAsync("BackgroundWorker");
    }
}


If you see in the above button click handler we are invoking the BackgroundWorker instance by calling the RunWorkerAsync method, which will create another thread implictly and call the DoWork event handler in it; once completed it will invoke the RunWorkerCompleted event handler marshalled back to the UI thread.

Put a break point in th DoWork event handler and RunWorkerCompleted event handler:

CTCWinFor2.gif

So how do we confirm what I said above: run the code with debugger(F5): when the breakpoint is hit, open the "Threads" window from your devenv and you will see that the cursor at "Worker Thread" in the category column and just above that you will see the "Main Thread" i.e. our current execution is not happening in the main UI thread but instead in another thread which is churned out by Background worker class.

CTCWinFor3.gif

Now let us hit F5 and let the debugger reach the RunWorkerCompleted event handler where we have set another debug point and inspect the "Threads" window again:

CTCWinFor4.gif

The arrow now points back to the "Main thread" indicating that the call is marshaled back to the UI thread i.e. the "Main Thread" in the window.

So you see that the BackgroundWorker Class internally takes care of marshaling the requests back to the UI threads.

I will urge the readers to go ahead and try to access any UI control in DoWork event handlers to see the "Cross thread exceptions" being thrown that will give them understanding of why we had to do this.  


Similar Articles