Click here to Skip to main content
15,867,835 members
Articles / Desktop Programming / Windows Forms
Article

Making Windows Forms thread safe

Rate me:
Please Sign up or sign in to vote.
4.83/5 (17 votes)
15 Feb 20055 min read 151.8K   1.7K   92   11
An attempt to simplify multi threaded Windows Forms applications.

Introduction

Programming Windows Forms user interfaces is quite straightforward as long as you do not use multiple threads. But whenever your application has got some actual work to do, it becomes necessary to use threading to ensure the responsiveness of the UI. This is where Windows Forms programming can get quite complex.

The problem

As you know, Windows Forms is not thread safe in general. For example, it is not safe to get or set a property on a Windows.Forms control from any thread except the thread that handles the message queue. It is absolutely essential that you only make modifications to your Windows Forms controls from the message queue thread.

The standard solution

There is, of course, a mechanism to deal with this. Each Windows Forms control has the InvokeRequired property which returns false if the current thread is the message queue thread. And there is the Invoke method which makes it possible to enqueue a delegate complete with parameters into the message queue of the control.

Since the delegate is called directly from the message queue, no threading issues arise. But this style of programming can be quite tedious. Just to do something as simple as setting a text property or enabling/disabling a control, you have to define a separate method with a matching delegate.

Example: Random Strings

To illustrate this approach, I wrote a small Windows Forms program that generates random strings. Here is an excerpt of the code which shows how synchronization between the worker thread and the message loop thread is done.

C#
char PickRandomChar(string digits) 
{ 
    Thread.Sleep(100); 
    return digits[random.Next(digits.Length)]; 
} 
delegate void SetBoolDelegate(bool parameter); 
void SetInputEnabled(bool enabled)
{
    if(!InvokeRequired)
    {
        button1.Enabled=enabled; 
        comboBoxDigits.Enabled=enabled; 
        numericUpDownDigits.Enabled=enabled;
    } 
    else 
        Invoke(new SetBoolDelegate(SetInputEnabled),new object[] {enabled}); 
} 
delegate void SetStringDelegate(string parameter);
void SetStatus(string status) { 
    if(!InvokeRequired)
        labelStatus.Text=status; 
    else 
        Invoke(new SetStringDelegate(SetStatus),new object[] {status});
} 
void SetResult(string result) {
    if(!InvokeRequired)
        textBoxResult.Text=result; 
    else
        Invoke(new SetStringDelegate(SetResult),new object[] {result});
} 
delegate int GetIntDelegate(); 
int GetNumberOfDigits()
{
    if(!InvokeRequired) 
        return (int)numericUpDownDigits.Value;
    else 
        return (int)Invoke(new GetIntDelegate(GetNumberOfDigits),null);
} 
delegate string GetStringDelegate(); 
string GetDigits()
{
    if(!InvokeRequired) 
        return comboBoxDigits.Text; 
    else
        return (string)Invoke(new GetStringDelegate(GetDigits),null);
}
void Work() 
{
    try 
    {
        SetInputEnabled(false);
        SetStatus("Working");        
        int n=GetNumberOfDigits();
        string digits=GetDigits();
        StringBuilder text=new StringBuilder();
        for(int i=0;i!=n;i++)
        {
            text.Append(PickRandomChar(digits));
            SetResult(text.ToString());
        }
        SetStatus("Ready");
    }
    catch(ThreadAbortException) 
    {
        SetResult("");
        SetStatus("Error");
    }
    finally 
    {
        SetInputEnabled(true);
    }
}
void Start() 
{
    Stop();
    thread=new Thread(new ThreadStart(Work));
    thread.Start();
}
void Stop() 
{
    if(thread!=null) 
    {
        thread.Abort();
        thread=null;
    }
}

I used Thread.Abort because in this case it is the simplest solution. If you do something that should not be interrupted under any circumstances, you should signal the thread using a flag instead.

This is a lot of simple but very repetitive code. Note that you always have to check InvokeRequired because calling Invoke before the message queue is created can lead to an error.

Generating thread-safe wrappers

In an earlier article, I showed how it is possible to automatically create wrappers for classes to make them "implicitly" implement an interface. The same code generation method can be extended to create wrappers that automatically ensure that methods are called in the right thread.

I will describe in detail how the whole thing works, later. First, let's take a look at how the mechanism is used.

First you expose the relevant properties in your form without considering threading issues. This is something you would probably want to do even if you would not use multithreading at all.

C#
public bool InputEnabled
{
    set
    { 
        button1.Enabled=value; 
        comboBoxDigits.Enabled=value; 
        numericUpDownDigits.Enabled=value;
    } 
}
public string Status 
{
    set { labelStatus.Text=value;} 
}
public int NumberOfDigits
{
    get { return numericUpDownDigits.Value; }
}
public string Digits 
{
    get { return comboBoxDigits.Text; }
}
public string Result 
{
    set { textBoxResult.Text=value; }
}

Then you define an interface which contains all the properties and/or methods you may want to access from a different thread.

C#
interface IFormState
{
    int NumberOfDigits { get; } 
    string Digits { get; }
    string Status { set; }
    string Result { set; }
    bool InputEnabled { set; }
}

Now in the worker method, all you have to do is create a thread-safe wrapper and use it. All the repetitive code will be emitted for you.

C#
void Work() 
{
    IFormState state=Wrapper.Create(typeof(IFormState),this);
    try 
    {
        state.InputEnabled=false;
        state.Status="Working";
        int n=state.NumberOfDigits;
        string digits=state.Digits;
        StringBuilder text=new StringBuilder();

        for(int i=0;i<n;i++) 
        {
            text.Append(PickRandomChar(digits));
            state.Result=text.ToString();
        }   
        state.Status="Ready";
    }
    catch(ThreadAbortException) 
    {
        state.Status="Error";
        state.Result=""; 
    }
    finally
    {
        state.InputEnabled=true;
    }
}

How it works

The wrapper generator uses System.Reflection.Emit to generate a proxy class that contains all methods required by the interface. This also includes property accessor methods that have a special signature.

The body of these methods will call the original method directly if InvokeRequired returns false. This is important to make sure that calling the methods does also work if the form is not yet attached to a message handling thread.

If InvokeRequired returns true, a delegate pointing to the original method is created and passed to the Invoke method of the form. Delegate types are cached so that you don't get multiple delegate types for the same method signature.

Since the wrapper generator uses the ISynchronizeInvoke interface for synchronizing invokes, you can also use it in a non-Windows-Forms application. All you would have to do is implement the interface and presumably implement some kind of message queue yourself.

Limitations and Caveats

It is important to understand that while the thread-safe wrapper hides the thread synchronization overhead, it does not make it go away. So accessing a property using a thread safe wrapper will be much slower than accessing it directly if InvokeRequired is true. So if you have to make multiple complex changes to your form from a different thread, it is best to make them all in one method instead of using separate property accessor calls.

Another thing to keep in mind is that not every kind of object can be safely passed from one thread to another without synchronization. In general, it is only safe to pass value types like int, DateTime etc. and immutable reference types like string. You have to be really careful with passing mutable reference types like StringBuilder from one thread to another. This can be OK if you are really sure that the object is not modified while a reference exists in different threads or if the object is thread safe. If in doubt, just pass a deep copy instead of a reference.

Whidbey

Whidbey will make multithreading issues easier since it has additional support for background processing in Windows Forms, and especially since it makes working with delegates much easier by supporting anonymous methods, which are really closures.

Setting a property in Whidbey is much simpler:

C#
Invoke(delegate { labelStatus.Text="Working"; });

Getting a property as well:

C#
int n=(int)Invoke(delegate { return numericUpDownDigits.Value; });

Unfortunately, Whidbey will probably be released bundled with Duke Nukem Forever in the year 2025.

References

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
Germany Germany
Rüdiger Klaehn works as freelance developer in the space industry. He is very interested in functional programming languages and hopes that .NET will lead to a more widespread adoption in the industry.

Comments and Discussions

 
QuestionThread safe Pin
Traian Patrusel20-May-07 23:30
Traian Patrusel20-May-07 23:30 

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.