Improper Usage of Invoke
Introduction
Asynchronous programming is great. You can easily use the ThreadPool and other threading constructs to do background work while keeping your UI responsive to the user. But, improper use of Invoke()
can cause your UI to appear locked up. Here's a short guide on how to not communicate with your UI thread.
Thread Safety and the UI
For performance reasons, MS decided not to make instances of controls thread safe (in fact, practically, no object instances in the framework are thread safe; all static objects and methods are, however). Controls also need access to the message pump (started by Application.Run()
) in order to function properly. So, the initial thread that executes when your program runs (check the Main
function in program.cs (.NET 2.0) or in your main form (.NET 1.1 and earlier)) and that creates the initial form and starts the message pump is thus referred to as the "UI Thread". It is associated with the message pump and any forms and other controls it creates.
Controls know what thread they were created on, and check to make sure that any calls that manipulate their state are run by this thread. Any attempt to manipulate that state from a thread other than the UI thread will cause the control to throw an InvalidOperationException
. To allow for this cross-thread modification of the UI, the Control
class offers the InvokeRequired
property and the Invoke
(and BeginInvoke
, etc.) method.
Invoke and InvokeRequired
Invoke
(and BeginInvoke
, etc) and InvokeRequired
are inherited from System.Windows.Forms.Control
. That means that you can use them on any Windows Forms control, from a lowly Label
all the way up to a Form
.
When you check InvokeRequired
, the control (Form
or otherwise) retrieves the window thread process ID of its parent control (this propagates up to the initial parent, I believe). It then compares this ID with the ID of the calling thread. If they do not match, this method returns true
(it may return false
when the parent control's handle doesn't exist, but that's a rare occurrence that is covered in the documentation and in my code sample). This tells you that the current thread cannot safely manipulate the control. The solution to this problem is to send a message to the UI thread telling it to execute a method for you. This is what Invoke
(and its siblings) does for you.
Using Invoke and InvokeRequired
The Invoke
method essentially (looking at the method in Reflector leads me to keep it simple!) posts a message to the UI thread's message pump, asking it to execute the method passed to Invoke
via a delegate (function pointer). When this message is read by the UI thread's message pump, the UI thread executes the method pointed at by the passed delegate, which will be run by the UI thread and is therefore free to modify the control. Calling Invoke
will block the calling thread until the UI thread has completed its task. Calling BeginInvoke
will return immediately so that the calling thread can continue processing while the UI thread services the request asynchronously (not calling EndInvoke
will not leak resources in this sole case, so it is safe to ignore this part of the Asynchronous Programming Model).
The Problem with Invoke
Calling Invoke
can cause a bottleneck that will result in your program becoming non-responsive, if you're not careful. The following code snippet (from the sample) will demonstrate this:
public Form1()
{
InitializeComponent();
// Invoke does not work without the control
// handle first being created. Since I am
// starting the worker threads in the constructor,
// I have to force creation of this handle early.
// Remove this code and see what happens!
while (!IsHandleCreated)
{
// force handle creation
IntPtr temp = Handle;
}
ThreadPool.QueueUserWorkItem(Foo);
ThreadPool.QueueUserWorkItem(Foo);
ThreadPool.QueueUserWorkItem(Foo);
}
public void Foo(object o)
{
while (true)
{
rtb.Invoke(new StringDelegate(UpdateDisplay),
new object[] { "Foo!\n" });
//Thread.Sleep(10);
}
}
public void UpdateDisplay(string text)
{
this.rtb.Text += text;
}
public delegate void StringDelegate(string foo);
Since I know that Foo
is only called from worker threads, I don't bother to check InvokeRequired
. Notice that I call Handle
to explicitly cause the form to create its window handle, without which Invoke
will not work (this is usually not needed; the fact that I start the worker threads in the constructor necessitates this).
This code demonstrates that worker threads pounding the message pump has the same effect as running the code synchronously via the UI thread. The message pump becomes clogged by calls to run UpdateDisplay
, and the worker threads get blocked while waiting for their Invoke
s to be serviced. In a situation like this, you would want to add Thread.Sleep
to the worker threads, indicating a small amount (like 10ms) to give the CPU time to block the worker thread and allow the UI thread to process some of its messages.
Conclusion
Using Invoke
is a quick and easy way to allow your background threads to modify your UI. But, if you aren't careful, you may cause your UI to lock up just as if it were doing all the work in the first place.