Share via


Safe, Simple Multithreading in Windows Forms, Part 1

 

Chris Sells

June 28, 2002

Download the AsynchCalcPi.exe sample.

It all started innocently enough. I found myself needing to calculate the area of a circle for the first time in .NET. This called, of course, for an accurate representation of pi. System.Math.PI is handy, but since it only provides 20 digits of precision, I was worried about the accuracy of my calculation (I really needed 21 digits to be absolutely comfortable). So, like any programmer worth their salt, I forgot about the problem I was actually trying to solve and I wrote myself a program to calculate pi to any number of digits that I felt like. What I came up with is shown in Figure 1.

Figure 1. Digits of Pi application

Progress on Long-Running Operations

While most applications don't need to calculate digits of pi, many kinds of applications need to perform long-running operations, whether it's printing, making a Web service call, or calculating interest earnings on a certain billionaire in the Pacific Northwest. Users are generally content to wait for such things, often moving to something else in the meantime, so long as they can see that progress is being made. That's why even my little application has a progress bar. The algorithm I'm using calculates pi nine digits at a time. As each new set of digits are available, my program keeps the text updated and moves the progress bar to show how we're coming along. For example, Figure 2 shows progress on the way to calculating 1000 digits of pi (if 21 digits are good, than 1000 must be better).

Figure 2. Calculating pi to 1000 digits

The following shows how the user interface (UI) is updated as the digits of pi are calculated:

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  _pi.Text = pi;
  _piProgress.Maximum = totalDigits;
  _piProgress.Value = digitsSoFar;
}

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

  // Show progress
  ShowProgress(pi.ToString(), digits, 0);

  if( digits > 0 ) {
    pi.Append(".");

    for( int i = 0; i < digits; i += 9 ) {
      int nineDigits = NineDigitsOfPi.StartingAt(i+1);
      int digitCount = Math.Min(digits - i, 9);
      string ds = string.Format("{0:D9}", nineDigits);
      pi.Append(ds.Substring(0, digitCount));

      // Show progress
      ShowProgress(pi.ToString(), digits, i + digitCount);
    }
  }
}

Everything was going along fine until, in the middle of actually calculating pi to 1000 digits, I switched away to do something else and then switched back. What I saw is shown in Figure 3.

Figure 3. No paint event for you!

The problem, of course, is that my application is single-threaded, so while the thread is calculating pi, it can't also be drawing the UI. I didn't run into this before because when I set the TextBox.Text and ProgressBar.Value properties, those controls would force their painting to happen immediately as part of setting the property (although I noticed that the progress bar was better at this than the text box). However, once I put the application into the background and then the foreground again, I need to paint the entire client area, and that's a Paint event for the form. Since no other event is going to be processed until we return from the event we're already processing (that is, the Click event on the Calc button), we're out of luck in terms of seeing any further progress. What I really needed to do was free the UI thread for doing UI work and handle the long-running process in the background. For this, I need another thread.

Asynchronous Operations

My current synchronous Click handler looked like this:

void _calcButton_Click(object sender, EventArgs e) {
  CalcPi((int)_digits.Value);
}

Recall that the issue is until CalcPi returns, the thread can't return from our Click handler, which means the form can't handle the Paint event (or any other event, for that matter). One way to handle this is to start another thread, like so:

using System.Threading;
…
int _digitsToCalc = 0;

void CalcPiThreadStart() {
  CalcPi(_digitsToCalc);
}

void _calcButton_Click(object sender, EventArgs e) {
  _digitsToCalc = (int)_digits.Value;
  Thread piThread = new Thread(new ThreadStart(CalcPiThreadStart));            
  piThread.Start();
}

Now, instead of waiting for CalcPi to finish before returning from the button Click event, I'm creating a new thread and asking it to start. The Thread.Start method will schedule my new thread as ready to start and then return immediately, allowing our UI thread to get back to its own work. Now, if the user wants to interact with the application (put it in the background, move it to the foreground, resize it, or even close it), the UI thread is free to handle all of those events while the worker thread calculates pi at its own pace. Figure 4 shows the two threads doing the work.

Figure 4. Naive multithreading

You may have noticed that I'm not passing any arguments to the worker thread's entry point—CalcPiThreadStart. Instead, I'm tucking the number of digits to calculate into a field, _digitsToCalc, calling the thread entry point, which is calling CalcPi in turn. This is kind of a pain, which is one of the reasons that I prefer delegates for asynchronous work. Delegates support taking arguments, which saves me the hassle of an extra temporary field and an extra function between the functions I want to call.

If you're not familiar with delegates, they're really just objects that call static or instance functions. In C#, they're declared using function declaration syntax. For example, a delegate to call CalcPi looks like this:

delegate void CalcPiDelegate(int digits);

Once I have a delegate, I can create an instance to call the CalcPi function synchronously like so:

void _calcButton_Click(object sender, EventArgs e) {
  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
  calcPi((int)_digits.Value);
}

Of course, I don't want to call CalcPi synchronously; I want to call it asynchronously. Before I do that, however, we need to understand a bit more about how delegates work. My delegate declaration above declares a new class derived from MultiCastDelegate with three functions, Invoke, BeginInvoke, and EndInvoke, as shown here:

class CalcPiDelegate : MulticastDelegate {
  public void Invoke(int digits);
  public void BeginInvoke(int digits, AsyncCallback callback,
                          object asyncState);
  public void EndInvoke(IAsyncResult result);
}

When I created an instance of the CalcPiDelegate earlier and then called it like a function, I was actually calling the synchronous Invoke function, which in turn called my own CalcPi function. BeginInvoke and EndInvoke, however, are the pair of functions that allow you to invoke and harvest the results of a function call asynchronously. So, to have the CalcPi function called on another thread, I need to call BeginInvoke like so:

void _calcButton_Click(object sender, EventArgs e) {
  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
  calcPi.BeginInvoke((int)_digits.Value, null, null);
}

Notice that we're passing nulls for the last two arguments of BeginInvoke. These are needed if we'd like to harvest the result from the function we're calling at some later date (which is also what EndInvoke is for). Since the CalcPi function updates the UI directly, we don't need anything but nulls for these two arguments. If you'd like the details of delegates, both synchronous and asynchronous, see .NET Delegates: A C# Bedtime Story.

At this point, I should be happy. I've got my application to combine a fully interactive UI that shows progress on a long-running operation. In fact, it wasn't until I realized what I was really doing that I became unhappy.

Multithreaded Safety

As it turned out, I had just gotten lucky (or unlucky, depending on how you characterize such things). Microsoft Windows® XP was providing me with a very robust implementation of the underlying windowing system on which Windows Forms is built. So robust, in fact, that it gracefully handled my violation of the prime directive of Windows programming—Though shalt not operate on a window from other than its creating thread. Unfortunately there's no guarantee that other, less robust implementations of Windows would be equally graceful given my bad manners.

The problem, of course, was of my own making. If you remember Figure 4, I had two threads accessing the same underlying window at the same time. However, because long-running operations are so common in Windows application, each UI class in Windows Forms (that is, every class that ultimately derives from System.Windows.Forms.Control) has a property that you can use from any thread so that you can access the window safely. The name of the property is InvokeRequired, which returns true if the calling thread needs to pass control over to the creating thread before calling a method on that object. A simple Assert in my ShowProgress function would have immediately shown me the error of my ways:

using System.Diagnostics;

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  // Make sure we're on the right thread
  Debug.Assert(_pi.InvokeRequired == false);
  ...
}

In fact, the .NET documentation is quite clear on this point. It states, "There are four methods on a control that are safe to call from any thread: Invoke, BeginInvoke, EndInvoke, and CreateGraphics. For all other method calls, you should use one of the invoke methods to marshal the call to the control's thread." So, when I set the control properties, I'm clearly violating this rule. And from the names of the first three functions that I'm allowed to call safely (Invoke, BeginInvoke, and EndInvoke), it should be clear that I need to construct another delegate that will be executed in the UI thread. If I were worried about blocking my worker thread, like I was worried about blocking my UI thread, I'd need to use the asynchronous BeginInvoke and EndInvoke. However, since my worker thread exists only to service my UI thread, let's use the simpler, synchronous Invoke method, which is defined like this:

public object Invoke(Delegate method);
public object Invoke(Delegate method, object[] args);

The first overload of Invoke takes an instance of a delegate containing the method we'd like to call in the UI thread, but assumes no arguments. However, the function we want to call to update the UI, ShowProgress, takes three arguments, so we'll need the second overload. We'll also need another delegate for our ShowProgress method so that we can pass the arguments correctly. Here's how to use Invoke to make sure that our calls to ShowProgress, and therefore our use of our windows, shows up on the correct thread (making sure to replace both calls to ShowProgress in CalcPi):

delegate
void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar);

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

  // Get ready to show progress asynchronously
  ShowProgressDelegate showProgress =
    new ShowProgressDelegate(ShowProgress);

  // Show progress
  this.Invoke(showProgress, new object[] { pi.ToString(), digits, 0});

  if( digits > 0 ) {
    pi.Append(".");

    for( int i = 0; i < digits; i += 9 ) {
      ...
      // Show progress
      this.Invoke(showProgress,
        new object[] { pi.ToString(), digits, i + digitCount});
    }
  }
}

The use of Invoke has finally given me a safe use of multithreading in my Windows Forms application. The UI thread spawns a worker thread to do the long-running operation, and the worker thread passes control back to the UI thread when the UI needs updating. Figure 5 shows our safe multithreading architecture.

Figure 5. Safe multithreading

Simplified Multithreading

The call to Invoke is a bit cumbersome, and because it happens twice in our CalcPi function, we could simplify things and update ShowProgress itself to do the asynchronous call. If ShowProgress is called from the correct thread, it will update the controls, but if it's called from the incorrect thread, it uses Invoke to call itself back on the correct thread. This lets us go back to the previous, simpler CalcPi:

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  // Make sure we're on the right thread
  if( _pi.InvokeRequired == false ) {
    _pi.Text = pi;
    _piProgress.Maximum = totalDigits;
    _piProgress.Value = digitsSoFar;
  }
  else {
    // Show progress asynchronously
    ShowProgressDelegate showProgress =
      new ShowProgressDelegate(ShowProgress);
    this.Invoke(showProgress,
      new object[] { pi, totalDigits, digitsSoFar});
  }
}

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

  // Show progress
  ShowProgress(pi.ToString(), digits, 0);

  if( digits > 0 ) {
    pi.Append(".");

    for( int i = 0; i < digits; i += 9 ) {
      ...
      // Show progress
      ShowProgress(pi.ToString(), digits, i + digitCount);
    }
  }
}

Because Invoke is a synchronous call and we're not consuming the return value (in fact, ShowProgress doesn't have a return value), it's better to use BeginInvoke here so that the worker thread isn't held up, as shown here:

BeginInvoke(showProgress, new object[] { pi, totalDigits, digitsSoFar});

BeginInvoke is always preferred if you don't need the return of a function call because it sends the worker thread to its work immediately and avoids the possibility of deadlock.

Where Are We?

I've used this short example to demonstrate how to perform long-running operations while still showing progress and keeping the UI responsive to user interaction. To accomplish this, I used one asynch delegate to spawn a worker thread and the Invoke method on the main form, along with another delegate to be executed back in the UI thread.

One thing I was very careful never to do was to share access to a single point of data between the UI thread and the worker thread. Instead, I passed a copy of the data needed to do the work to the worker thread (the number of digits), and a copy of the data needed to update the UI (the digits calculated so far and the progress). In the final solution, I never passed references to objects that I was sharing between the two threads, such as a reference to the current StringBuilder (which would have saved me a string copy for every time I went back to the UI thread). If I had passed shared references back and forth, I would have had to use .NET synchronization primitives to make sure to that only one thread had access to any one object at a time, which would have been a lot of work. It was already enough work just to get the calls happening between the two threads without bringing synchronization into it.

Of course, if you've got large datasets that you're working with you're not going to want to copy data around. However, when possible, I recommend the combination of asynchronous delegates and message passing between the worker thread and the UI thread for implementing long-running tasks in your Windows Forms applications.

Acknowledgments

I'd like to thank Simon Robinson for his post on the DevelopMentor .NET mailing list that inspired this article, Ian Griffiths for his initial work in this area, Chris Andersen for his message-passing ideas, and last but certainly not least, Mike Woodring for the fabulous multithreading pictures that I lifted shamelessly for this article.

References

Chris Sells is an independent consultant, specializing in distributed applications in .NET and COM, as well as an instructor for DevelopMentor. He's written several books, including ATL Internals, which is in the process of being updated for ATL7. He's also working on Essential Windows Forms for Addison-Wesley and Mastering Visual Studio .NET for O'Reilly. In his free time, Chris hosts the Web Services DevCon and directs the Genghis source-available project. More information about Chris, and his various projects, is available at http://www.sellsbrothers.com.