Level - Beginner to Intermediate
Audience - .NET WinForm Developers
The article doesn't get into multithreading itself, I'll leave that for another potential article, it focuses only on the proper way to make your WinForm work properly with multi-threading.
Recently I couldn’t remember how to make a WinForm have a responsive user interface that was updated by multiple threads under the covers. It’d been a while, and in my search, I quickly found the code I needed but I realized that there’s a reason why I regularly get questions about how to do this, about how to use threads, etc. it's because all the articles that I kept finding were all at the basic level, it didn't really show someone where you can go from there. It inspired me, and given that I was starting a technical blog, I thought it would be a good place to start.
For the person like myself who is looking for the quick answer, here it is:
// This is your event handler for some other event that the WinForm subscribes to.
void SomeEventHandler(MyArgs args)
{
// This gets invoked by the caller's thread, and not the Winform's, therefore
// it is not appropriate to update the WinForm here.
// Create an event handler Delegate
UIUpdaterDelegate updateUI = new UIUpdaterDelegate(UpdateUI);
// Now ‘schedule’ the update for when the Winform (i.e. this) is able to by calling
// the Winform's Invoke method.
this.Invoke(updateUI, new object[] { args });
}
// Here's the Delegate which uses an arbitrary parameter I created which allows me to
// pass whatever information I need. Note that there really isn't any point to being non-void
// because no one else is calling this. I can put as many parameters as I want, just include them
// in the object array above in (see the this.Invoke ... code)
delegate void UIUpdaterDelegate(MyArgs args);
// Here is the “Real“ Event handler for the event that the Winform subscribed to with the
// SomeEventHandler. It shouldn't be visible to anyone else, i.e. it's protected or use private.
protected void UpdateWithUIThread(MyArgs args) {
// Do the actual updating here
}
So now that the people who were looking for the quick answer are satisfied, let’s break this down. First off, why do this? And then why not just use the Background Worker thread?
Why?
While tweaking your applications business logic and your stored procedures is great and necessary in improving the performance of an application, you can only go so far and still likely end up with dissatisfied users. Often developers aren't sure what to do next and end up either trying to sell the user to “live with it”, or try some very significant refactoring of the application which tends to be lots of hours for little benefit, or end up recommending to improve the hardware. This also isn't a sufficient answer.
Very often the answer to the user's woes is incorporating multi-threading into the user interface, but that's not a trivial thing to do, particularly when you look at the documentation that's available. Returning control to the user to allow them to continue working, even if it is only on a limited amount of things while the background process is working, can make a world of difference. By the way you don't need to do this for every form, just the ones where you need it.
Also I should note that this approach also allows you to have your form subscribed for “interruption type events”, i.e. IF something happens let my WinForm know so that I can tell the user. An example of this could be a type of messenger application built into your app that informs the user “The customer record you are working on has just been checked out by Betty Simons” or “There is a new version of this application now available, would you like to get the update now?” There are many examples of where and when you might want or need to have your WinForm watch for something and need to get updated by another thread that's doing the actual watching.
Why Not The Background Worker
In .NET 2.0 (VS.NET 2005) there's a component called the Background Worker. At first I tried using this but I learned that it is very much geared to incremental, countable progress, fine for updating progress bars but not good enough as a general 'working directly with threads' replacement. Also, I found that it behaved a lot less predictably for my asynchronous UI updates than using threads directly, which was unacceptable for me and my clients. If it works great for you, please share with me where and how you use it as I haven't really seen any concrete areas of value of this control given the limitations that I ran into.
The Winform Thread
Updating the WinForm looks relatively straight forward, particularly when you aren't dealing with threads, but there's something going on that most of us aren't aware of at first, that when we step into Threading, trips us up badly. There's a thread that runs in our applications that is created by .NET automatically that is responsible for managing the interaction with the user and updating the form to reflect what they are doing. If they drag it, if they click on a drop down list, if they maximize it, it is that thread that makes sure the work gets done. When the user clicks on a button, it is that thread that runs the code for that button click method and it is this reason why the user can't do anything until that button click method finishes, because there's no thread to deal with what they are trying to do. It's like asking someone at the fast food 'counter interface' to get you a sandwich and wondering why the 'counter interface' isn't responsive while they are off getting you the sandwich, it's simply because there's isn't anyone there.
You'd consider the interface to be much more responsive if the 'counter interface' took your sandwich request, handed it over to someone else to go, and then asked you if you'd like anything else. Possibly as you continued your order, or afterwards when you were waiting, your sandwich would arrive. You'd have the feeling that it was quite because you just finished ordering your drink or fries and then 'bang' here is your sandwich and a moment later, the rest of your order.
When we first get into threading, and not realizing that forms are not designed to be updated only by the UI thread, we figure that we'll create a method on the WinForm that looks like this:
// Anyone can call this to update my UI and it won't affect the user responsiveness!!
public void UpdateProgressInfo (MyArgs args) {
statusLabel.Text = “Received Updated information at “+ DateTime.Now.ToString();
progressBar.Value += 1;
}
The problem with this is that it yields unpredictable results, and they drive you crazy trying to debug them because it “just happened once, no there it is again. Why the heck does it say “Received Upeleted record?” The problem arises because the user will do something that updates the user interface somehow and the UI Thread clashes with the thread that is calling your UpdateProgressInfo method, and a mess is created because the controls aren't thread safe. Even if you put a lock statement around it, it's still the wrong way to do things and can yield unexpected results.
Invoke the Winform
WinForm's have a method called Invoke which is your way of telling the form “Hey, when you have a sec, call this method with these parameters.” It literally looks like this:
this.Invoke(updateUI, new object[] { args });
You can pass whatever, and however many, parameters that you want, so args, args2, args3 of whatever types. Of course, the question is, when do I call this? In any event handler that your WinForm where it is handling the event that is on a threaded object. For example, say you have a TransactionProcessing object (transProcessingDaemon) that runs on its own thread or set of threads. Either in your WinForm's Init or Load there's a...
transProcessingDaemon.ProcessingDoneEvent += new MyTransProcessingEventDelegate(this.FakeUIUpdater)
Of course, your updateUI method needs to be in accordance with the MyTransProcessingEventDelegate definition, i.e. matching return type and matching parameter list.
The FakeUIUpdater, a bad name but there to illustrate the point, looks like this:
// This is your event handler for some other event that the WinForm subscribes to.
void FakeUIUpdater(MyArgs args)
{
// This gets invoked by the caller's thread, and not the Winform's, therefore
// it is not appropriate to update the WinForm here.
// Create an event handler Delegate
UIUpdaterDelegate updateUI = new UIUpdaterDelegate(UpdateUI);
// Now ‘schedule’ the update for when the Winform (i.e. this) is able to by calling
// the Winform's Invoke method.
this.Invoke(updateUI, new object[] { args });
}
The UIUpdaterDelegate referred above, is here
// Here's the Delegate which uses an arbitrary parameter I created which allows me to
// pass whatever information I need from the FakeUIUpdater to the real one. Note that there really
// isn't any point to being non-void because no one else is calling this. I can put as many
// parameters as I want, just include them in the object array above in (see the this.Invoke ... code)
delegate void UIUpdaterDelegate(MyArgs args);
The UpdateUI method looks like this:
protected void UpdateUI(MyArgs args) {
// Do the actual updating here
statusLabel.Text = “Received Updated information at “+ DateTime.Now.ToString();
progressBar.Value += 1;
}
I always make these methods protected or private, as no one else should be calling this method but of course.
This method will be called, i.e. invoked, by the UI Thread which means there will be no issues in terms of controls getting updated properly. So now when the ProcessingDoneEvent is raised, the WinForm's FakeUIUpdater method will be called which in turn 'schedules' the calling of the real method by the UI Thread.
So there you have it. At the end of the day, it's all about one method, the WinForm's Invoke, and about defining your delegates.
Have any feedback, thoughts, comments, ideas to improve this article? Drop me a line driss @ zouak.com