Tuesday, January 22, 2008

Cross threading in .NET

Working in a nice, modular, testable system sometimes presents new challenges. One of these is a timer in a class that begins a cascade of actions ending in updates to a form or control. The problem is the timer event is fired on a new thread, not the UI thread. You are not allowed to access UI components (anything derived from System.Control) from a thread other than the thread that created the component. This is a Windows issue, not a .NET issue, and it simplifies GUI programming. Fortunately the framework provides a way to get back to the UI thread. Every control has an Invoke method which takes a delegate as it's parameter, and an InvokeRequired property to tell you if you're on a thread other than the creator thread. So:


public delegate void SetTextDelegate(string text);

public void SetText(string text)
{
myTextBox.Text = text;
}

public void SetTextFromAnywhere(string text)
{
if (myTextBox.InvokeRequired)
{
myTextBox.Invoke(new SetTextDelegate(SetText), text);
}
else
{
SetText(text);
}
}


Keep in mind that the thread that calls Invoke will wait for the delegate to return. This can be the cause of some insidious deadlock and threadpool starving problems. A more robust multi-threading pattern is to use BeginInvoke, which schedules the delegate to executed, rather than executing it immediately. For you old C programmers, it's the difference between SendMessage and PostMessage. Also note that Invoke returns an object, which is whatever the delegate returns. With BeginInvoke you can use a delegate with a return value, but you must call EndInvoke to retrieve it.

No comments: