Click here to Skip to main content
15,890,947 members
Articles / Desktop Programming / Windows Forms

Another Way to Invoke UI from a Worker Thread

Rate me:
Please Sign up or sign in to vote.
4.75/5 (33 votes)
3 Oct 20056 min read 310K   2.2K   124   65
This article demonstrates an alternative way of invoking UI event handlers from a worker thread.

Image 1

Introduction

This article demonstrates an alternative way of invoking UI event handlers from a worker thread.

Events From Worker Threads - the "Traditional" Way

One of the benefits of the .NET platform is a much simpler way of performing lengthy tasks while keeping the user interface responsive. You can create an object, fill its properties and fields with proper data and make one of the object's methods a background thread. And just don't forget to retrieve the result of its work some time later.

When a background also known as worker thread needs to display some information in a UI, you just define an event, subscribe a UI object for this event and fire it in the background thread. The only problem is that the UI should be managed from its own thread that contains the message loop. Microsoft offers us a simple workaround.

Suppose we have an event handler like the following one:

C#
void OnEvent(object sender, EventArgs e)
{
    // Update the UI
}

It would work for any object derived from System.Windows.Forms.Control. To make it thread-safe, you should add a little bit of code:

C#
void OnEvent(object sender, EventArgs e)
{
    if(InvokeRequired)
        Invoke(new EventHandler(OnEvent), 
                    new object[] {sender, e});
    else
    {
        // Update the UI
    }
}

The InvokeRequired property of the Control class returns true if the method of the UI element is called from a different thread. In this case, we should use interthread marshalling using the Invoke method.

The "traditional" way of making multithreaded Windows Forms programs is good, but imagine you have a rather chatty worker object that has a lot of events. So for each event, you should modify your event handlers. I usually forget to do this and then hunt for bugs. I also think this workaround code is ugly and should be hidden from the worker's client. We should move it to the workers' methods and I'll show you how to do that.

Events From Worker Threads, an Easier Way

Microsoft uses a distinct pattern for raising events. Each event with the name AAA is accompanied with a protected method OnAAA that raises the event and can be overridden by the descendant classes. There are many reasons to use this pattern in your programs and I'm going to give you another one. Checking whether we are calling the UI from a non-UI thread should be performed in such a method.

You would ask how? Easy. The Control class actually implements System.ComponwentModel.ISynchronizeInvoke interface. This interface declares the property InvokeRequired and methods Invoke, BeginInvoke and EndInvoke so, in theory there are more message loop aware classes. While firing events, we need to check if each target object implements this interface. If it does, we need to check the InvokeRequired property and instead of calling the event delegate directly, we need to use the Invoke method. It means that for the event subscriber (a Form-derived class in most cases) this event will always be synchronous and the author of the subscriber won't need to bother about interthread marshalling.

However, we should keep in mind that all events are multicast delegates. Because of this, we must check all event subscriber objects separately. This is an easy part, because System.MulticastDelegate class has the GetInvocationList method that returns an array of single cast delegates that represent the combined multicast delegate.

Suppose we have an event declared as:

C#
public event EventHandler Event;

We should declare the accompanying method like this:

C#
protected virtual void OnEvent(EventArgs e)
{
    EventHandler handler = Event;
    if(null != handler)
    {
        foreach(EventHandler singleCast in handler.GetInvocationList())
        {
            ISynchronizeInvoke syncInvoke = 
                       singleCast.Target as ISynchronizeInvoke;
            try
            {
                if((null != syncInvoke) && (syncInvoke.InvokeRequired))
                    syncInvoke.Invoke(singleCast, 
                                  new object[] {this, e});
                else
                    singleCast(this, e);
            }
            catch
            {}
        }
    }
}

The first line of the method, the assignment statement, makes the method thread safe. I get a local copy of the event handler and make sure that even if somebody modifies the Event, there won't be any trouble. Then we check if there is any subscriber for the event. If there are subscribers, I check if the event's delegate target object implements the ISynchronizeInvoke interface. If it does and the object is in the UI thread, I perform interthread marshalling. In all other cases, I just call the delegate directly. I also catch all exceptions that a subscriber can throw. It is not a very good idea to ignore them, but so far I haven't found a good way to pass them to the OnAAA method caller.

Sample

In the sample, you'll find a simple component named Copier that copies one stream to another in the background thread. You might need a class like that if you copy big files or download data from Internet. The Copier class has some properties related to work progress, three thread-safe events Started, Progress and Finished and three public methods Start, Stop and Join that check the state of the Copier and if it is valid, delegates the work to the System.Threading.Thread method.

The sample also contains the ProgressForm class that provides a simple UI for the Copier component and the MainForm class that allows the user to specify the names of the source and target files. Note that the Copier does not close the data streams; this is the responsibility of the Copier's client. The same is true for the ProgressForm class.

There are two points of interest in this code. First is the implementation of the OnStarted, OnProgress and OnFinished methods, they follow the described pattern. Second is the ability to cancel the worker thread. Initially, I derived ProgressEventArgs class from CancelEventArgs, but the Control.Invoke call does not marshal its arguments back to the calling thread. I've added the Stop call to the Copier class that gracefully stops the worker thread.

I wrote the sample using SharpDevelop IDE with NAnt 0.85 as the build system. With NAnt, you should call Debug, Release or Doc targets; the latter creates the InvokeUI.chm HTML Help file in the doc folder. You can also use the build.bat file to compile the sample. Sorry, I have no Visual Studio and those of you who do will have to re-create the project. Just create an empty Windows Forms project and add all *.cs files to it.

License

Files Copier.cs, ProfressEvent.cs and ProgressForm.cs are covered by BSD-like license, see comments at the beginning of each file. The rest of the code is in public domain. Use the sample at your own risk.

Links

  • Here, you can learn about SharpDevelop and download your copy.
  • Here, you can learn about NAnt and download your copy. Personally, I recommend this tool.
  • MSDN article: "Defining an Event" - describes the Event/OnEvent pattern and shows how to optimize the event implementation by using System.ComponentModel.EventHandlerList class.
  • Articles by Chris Sells: "Safe, Simple Multithreading in Windows Forms" - simple and clear explanation on how multithreaded UI works in Windows Forms.
  • A blog entry by Patrick Cauldwell that describes a problem with InvokeRequired property.

Revision history

  • 03/09/2005: Initial post
  • 04/09/2005: Fixed a serious bug with background thread canceling; fixed a ProgressForm label issue; HtmlHelp compilation added to the build file

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Web Developer
Russian Federation Russian Federation
I'm a system administrator from Moscow, Russia. Programming is one of my hobbies. I presume I'm one of the first Russians who created a Web site dedicated to .Net known that time as NGWS. However, the Web page has been abandoned a long ago.

Comments and Discussions

 
GeneralRe: Thread Safe Pin
Alexey A. Popov14-Nov-05 7:01
Alexey A. Popov14-Nov-05 7:01 
GeneralRe: Thread Safe Pin
evildictaitor10-Sep-06 13:16
evildictaitor10-Sep-06 13:16 
GeneralRe: Thread Safe Pin
Alexey A. Popov10-Sep-06 22:56
Alexey A. Popov10-Sep-06 22:56 
GeneralRe: Thread Safe Pin
evildictaitor14-Sep-06 21:33
evildictaitor14-Sep-06 21:33 
GeneralRe: Thread Safe Pin
Alexey A. Popov15-Sep-06 0:07
Alexey A. Popov15-Sep-06 0:07 
GeneralRe: Thread Safe Pin
evildictaitor18-Sep-06 5:41
evildictaitor18-Sep-06 5:41 
GeneralRe: Thread Safe Pin
Alexey A. Popov19-Sep-06 6:31
Alexey A. Popov19-Sep-06 6:31 
GeneralRe: Thread Safe Pin
evildictaitor20-Sep-06 3:17
evildictaitor20-Sep-06 3:17 
From the C# 2.0 specification:

20.8.8 The lock statement
The lock statement may be used with an expression whose type is given by a type parameter, provided the type parameter is known to be a reference type (§‎20.7).

You will also see under the C# 1.4 specification and later the C# 2.0 specification that an event is a "safe-context" method delegate array, i.e. it is a private Delegate[] with special features to prevent them being called from outside of the classes private and public members and overloads only the += and -= to avoid anonymous methods being lost.

You can see this under the C# 2.0 specification as (21.3) Anonymous method conversions which is the precursor to the event keyword. This is shown in section 19.2 Anonymous methods and I quote:

Event handlers and other callbacks are often invoked exclusively through delegates and never directly

The difference between the fixed and the locked statement is subtle, and having read up on it I have come the conclusion that previously neither one of us fully understood the distinction.

A fixed variable is one which is not allowed to be moved by the internal RAM-de-fragmentation procedure run by the garbage collection in order to collect availiable space at the end of the program heap. A Locked statement prevents two remote threads accessing a method simulteniously, and is a throwover from the SQL implementation and is mainly used in heavy duty databases. The Invoke method does not need to use the locked keyword because the main thread is the only thread that accesses the shared memory with an intent to go through it and syncronously do the events (thus Application.DoEvents()). Because Invoke works syncronously the locked keyword will only serve to make code less readable.

You state that these operations replace the event's delegate reference with completely new instances. This is patently not true. If I wanted to create a new instance, I would follow the Microsoft C# formal reccommendation for anonymous methods, which states I should use code simmilar to the following. Notice the use of the new delegate.

class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate {
listBox.Items.Add(textBox.Text);
};
}
}

If you need additional reference as to how the difference between value-types and reference types such as the fact that delegate types are passed by reference, I would point you to the nine pages in the original C#1.0 draft (Chapter 4) on the subject. You may find it most informative.

The lock statement is discussed briefly in the C# definition, and you will find it more interesting to read the C++ definition of the keyword, which is where it derives from. The C# definition merely notes that it is useful for volatile fields such as are of use in drivers or databases. It is not reccommended that you use the lock keyword in Microsoft C#.

It is interesting to note that the Microsoft development team on MSDN do not, in fact, use the method you state that they use. The method I provided was actually from an MSDN article on syncronous events.

Also, despite your insistence of the usefulness of the lock keyword which Microsoft also disputes in its MSDN. I suggest you read articles such as http://msdn.microsoft.com/msdnmag/issues/05/10/MemoryModels/. You will find that they explicitly invented the delegate method with a view to removing the difficulties associated with the lock statement. The .NET Framework 2.0 shows the lack of the need for the lock statement as one of its proudest achievements. The article is written from a point of view of constructing the Microsoft C# language, and so many of the reccommendations have found themselves in the C# definition. The problems they discuss apply to C and C++, but not to C#. To quote from the article "The use of low-lock techniques in your application significantly increases the likelihood of introducing hard-to-find bugs".

MSDN also states that the use of raw threads is, itself, deprecated in C#3.0, and so the example I quote will become obsolete in december when C#3.0 enters the final development stage.

// statements_lock2.cs
using System;
using System.Threading;

class Account
{
int balance;

Random r = new Random();

public Account(int initial)
{
balance = initial;
}

int Withdraw(int amount)
{

// This condition will never be true unless the lock statement
// is commented out:
if (balance < 0)
{
throw new Exception("Negative Balance");
}

// Comment out the next line to see the effect of leaving out
// the lock keyword:
lock (this)
{
if (balance >= amount)
{
Console.WriteLine("Balance before Withdrawal : " + balance);
Console.WriteLine("Amount to Withdraw : -" + amount);
balance = balance - amount;
Console.WriteLine("Balance after Withdrawal : " + balance);
return amount;
}
else
{
return 0; // transaction rejected
}
}
}

public void DoTransactions()
{
for (int i = 0; i < 100; i++)
{
Withdraw(r.Next(1, 100));
}
}
}

class Test
{
public static void Main()
{
Thread[] threads = new Thread[10];
Account acc = new Account (1000);
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
threads[i] = t;
}
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}
}
}

Notice that the lock keyword is useful when dealing with raw threads, and yet it is not when dealing with events or delegates because of the .NET Framework 2.0.
GeneralRe: Thread Safe Pin
Alexey A. Popov22-Sep-06 7:06
Alexey A. Popov22-Sep-06 7:06 
GeneralRe: Thread Safe [modified] Pin
dragonfly_pl17-May-07 12:05
dragonfly_pl17-May-07 12:05 
GeneralUseful Work Pin
stedinb11-Oct-05 9:44
stedinb11-Oct-05 9:44 
GeneralRe: Useful Work Pin
Alexey A. Popov12-Oct-05 1:29
Alexey A. Popov12-Oct-05 1:29 
GeneralAlready Implemented... Pin
Tim McCurdy11-Oct-05 2:17
Tim McCurdy11-Oct-05 2:17 
GeneralRe: Already Implemented... Pin
Anonymous11-Oct-05 3:39
Anonymous11-Oct-05 3:39 
GeneralRe: Already Implemented... Pin
Alexey A. Popov11-Oct-05 7:04
Alexey A. Popov11-Oct-05 7:04 
GeneralRe: Already Implemented... Pin
Tim McCurdy11-Oct-05 7:50
Tim McCurdy11-Oct-05 7:50 
GeneralRe: Already Implemented... Pin
Alexey A. Popov12-Oct-05 1:28
Alexey A. Popov12-Oct-05 1:28 
GeneralRe: Already Implemented... Pin
Tim McCurdy12-Oct-05 3:33
Tim McCurdy12-Oct-05 3:33 
GeneralGreat! Pin
Paul Brower3-Oct-05 3:53
Paul Brower3-Oct-05 3:53 
GeneralRe: Great! Pin
Alexey A. Popov3-Oct-05 20:20
Alexey A. Popov3-Oct-05 20:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.