Historically, creating multithreaded applications had been challenging. There's a lot to consider - resource contention, performance, cross-thread invokations, and much more. Granted it is not 'difficult' to create an application that spins off secondary, tertiary, quaternary threads (or background threads for that matter) to accomplish 'work'. Resource contention, mutexes, semaphores, and locks aside, it's trivial to have code that spins up a thread. The implications, however, are farther reaching that this seemingless inocuous code.
Thread th = new Thread(new ThreadStart(workerMethod));
th.IsBackground = true;
th.Start();
The stakes are raised, however, when you have to deal with cross-thread invocations. User Interface (UI) programming is fraught with perils in this regard. Windows Forms are, for example, affinitized to their thread. That is, the only thread that can legally call into UI code and perform UI updates must be the thread on which the UI was created. (NOTE: a non-UI thread update may be successful from time to time. If you're doing this and are successful, I wouldn't be sitting too comfortably. At best it can lead to some very tricky bugs to track down. The UI might even become completely non-responsive). Usually, this isn't much of an issue. Unless, of course, you have an application that performs many operations on non-UI threads but you need to update the UI by providing some form of status from the non-UI thread.
Most (perhaps all - I haven't checked) of the Windows Forms controls are thread safe in their events. Even though a control might be multi-threaded, its events occur on the UI thread, so you're free to update the UI. If you write your own controls, however, this is something of which you must be cognizant. To help us in this battle, the .NET framework makes it pretty easy to make cross-thread calls, marshalling a call back onto the proper thread. It's easy, but if you don't know what you're looking for it might not be the easiest thing to find.
Let's talk about a couple of things. First, let's look at how to perform cross-thread calls to Windows Forms controls that is compatible with .NET 1.0, 1.1, and 2.0. Then we'll take a peek at some really cool 2.0 stuff that might be of some assistance.
Since the dawn of the .NET Framework, we've had a special interface called ISynchronizeInvoke. This interface, implemented by Windows Forms controls (such as the Form), provides a mechanism through which we can marshal a call onto the UI thread. There is a performance hit associated with such a call, so we really only want to make the call when we really have to. To that end, the designers of the interface saw fit to include a method called InvokeRequired which returns true if we're calling it from a non-UI thread, false otherwise.
Suppose we have a method named workerMethod() which may be running on a non-UI thread. Then again, it might not be, so we need to make the determination before updating the UI or raising an event to the caller. Note, events are not automatically marshalled to the UI thread - the call is made on the thread that raised the event in the first place. Suppose further that the method is running in-place on a Windows Form for simplicity.
internal sealed class SomeForm : Form {
private delegate void ReportProgress(string status);
private ReportProgress _reportProgress;
internal SomeForm() {
InitializeComponent();
_reportProgress = new ReportProgress(showStatus);
Thread th = new Thread(new ThreadStart(workerMethod));
th.IsBackground = true;
th.Start();
}
private void workerMethod() {
// do some work and then report progress
_reportProgress(“I'm doing something very important.“);
// do more work
_reportProgress(“Finished.“);
}
private void showStatus(string status) {
ISynchronizeInvoke sync = this as ISynchronizeInvoke;
if ( sync.InvokeRequired ) {
sync.Invoke(_reportProgress, new object[] { status });
return;
}
lblStatus.Text = status;
}
}
This simple example illustrates a few things. I took a shortcut (for performance reasons) of having a form-level delegate instance on which all invocations would occur. This instance (_reportProgress) provides a means to call the showStatus() method (rather than calling it directly) and allowing us to call back to (from itself) without having to create a new instance for each call.
Note that the showStatus() method casts the form to an ISynchronizeInvoke. It then determines whether or not the call was made on a non-UI thread. If it was (where InvokeRequired returns true), it then calls the Invoke() method (which transfers control to the UI thread synchronously) passing the parameter. This go around, the InvokeRequired property would return false and control would fall through to update the label appropriately.
This is a pretty slick strategy that I developed for a game I was writing a couple of years ago in which I had a lot of TCP/IP traffic on a special socket implementation that I had written. I needed a mechanism to update the UI with the various messages and this is an adaptation of my solution.
Tricky note: An interesting and quite difficult situation to track down when using this technique is that if you are trying to always marshal to a UI thread, but the control to which you are marshalling the call is not yet parented to a container form, InvokeRequired will ALWAYS return false - giving you a sense that you can go ahead an update the UI when in reality you cannot do so in this same manner. If anyone is interested, I have a great solution for this situation. With affermative feedback, I'd be happy to post my resolution.
In the .NET 2.0 days, now, Microsoft created a new class which can really help us out here. (Disclaimer: I have not investigated how it reacts when the recipient control is not yet parented as mentioned in the previous notice) The new class is called BackgroundWorker. It resides in the System.ComponentModel namespace. This class provides a pretty slick mechanism for running an operation on a non-UI thread and have it report updates on the proper UI thread (in a very similar manner to how I created a TCP/IP socket control). Essentially, you simply need to create the instance, set up the event handlers and invoke it.
internal sealed class SplashScreen : Form {
internal SplashScreen() {
InitializeComponent();
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
this.Show();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(initializeApplication);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(doneInitializing);
worker.ProgressChanged += new ProgressChangedEventHandler(reportInitializationProgress);
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
}
private void reportInitializationProgress(object sender, ProgressChangedEventArgs e) {
lblStatus.Text = (string)e.UserState;
}
private void doneInitializing(object sender, RunWorkerCompletedEventArgs e) {
// determine if an error was thrown.
// it would not be a good idea to display the messagebox directly within the worker method
// as it is not running on a UI thread.
if ( null != e.Error )
MessageBox.Show(this,
“Unable to initialize application.\n\n“ + e.Error.Message,
“Error Initializing“, MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close();
}
private void initializeApplication(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
// do something amazing but time consuming
worker.ReportProgress(0, “Doing some amazing work...“);
// do something more
worker.ReportProgress(50, “Continuing with our general amazingness...“);
// wrap up
worker.ReportProgress(100, “Done.“);
}
}
This simple example illustrates the sublime. With this simple class you can easily inject cross-thread programming into your Windows Forms applications with minimal pain.