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.
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.
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.
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.
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:
Invoke(delegate { labelStatus.Text="Working"; });
Getting a property as well:
int n=(int)Invoke(delegate { return numericUpDownDigits.Value; });
Unfortunately, Whidbey will probably be released bundled with Duke Nukem Forever in the year 2025.
References