Stop Polluting the UI Thread - Use a DelegateMarshaler
Introduction
Update - ThreadBarrier was a poorly chosen name, use the latest DelegateMarshaler implementation instead.
With the advent of multi-core processors becoming standard in desktop PCs, it is clear we are on the verge of a shift in which high level software is designed and developed. First there was Object Oriented programming, then design patterns, and now there is multithreading. Multithreading has been around for decades in high end computing, but due to the now wide availability of multiple cores in a standard PC or laptop, it is only now that it is beginning to gain mainstream attention. There is a lot of work to do for tools, frameworks, and patterns to simplify the design and debugging of multithreading programs. While Microsoft has been working diligently to improve their tools (Visual Studio) and frameworks (Task Parallel Library) to bring multithreading into the mainstream, there is not yet enough guidance and information on the internet about proper threading techniques and design patterns.
This is where the ‘ThreadBarrier’ pattern comes in. Ultimately it is a way to help simplify the interaction between the UI and worker threads. A ThreadBarrier is an encapsulation of thread execution inside a class such that it is not exposed to the outside world. Rather, external events are first posted to the UI thread so any listener (on the UI thread) does not have to worry about threading issues. Like any pattern, there are times where it is ideal to use and there are times where it does not apply. Use at your own discretion.
Background
It seems that nearly every introductory threading example on the internet consists of a button click that starts a thread that performs some operation in a worker thread in the UI code behind. While simple examples are good, this immediately introduces a developer to starting threads In UI code and thinking that such techniques are normal.
The Problem
The problem is that the only way most developers know how to get data back to the UI thread is to use a UI control to post back the information. In case you are new to threading: the golden rule is that all UI controls (windows, textboxes, progressbars, etc.) can only be accessed from the thread that created them (the UI thread of course). This means a worker thread cannot do myTextBox.Text=”asdf” otherwise a cross-thread exception will be thrown. Controls provide a mechanism for executing a method on the UI thread (Control.Invoke, Control.BeginInvoke, Dispatcher.Invoke, Dispatcher.BeginInvoke).
WinForms example:
delegate void SetStartDelegate(bool enabled);
public void SetStartEnabled(bool enabled)
{
if (this.InvokeRequired == false)
{
this.buttonStart.Enabled = enabled;
}
else
{
this.Invoke(new SetStartDelegate(SetStartEnabled), new object[] { enabled });
}
}
WPF example:
delegate void SetStartDelegate(bool enabled);
public void SetStartEnabled(bool enabled)
{
if (this.Dispatcher.CheckAccess() == true)
{
this.buttonStart.IsEnabled = enabled;
}
else
{
this.Dispatcher.Invoke(DispatcherPriority.Send, new SetStartDelegate(SetStartEnabled), enabled);
}
}
There are tricks to make this a bit cleaner, but that is the common way to update the UI from any thread.
The combination of the Control’s UI thread rule and a Control’s ability to invoke on the UI thread essentially handcuffs developers into mixing threads with the UI and trying to make it work. Perhaps some have tried to be smart and hide the threads away deep in non-UI code to keep the UI clean. Those developers probably end up with the following questions (at least I did):
“How the heck do you get a control for invoking that deep in the execution and far away from the UI code? And more importantly, why does it have to be this way? There has to be a better way.”
The Answer
There is a better way, and it involves a SynchronizationContext. Rather than use a Control to post execution back to the UI, a SynchronizationContext provides this same capability without the UI Control requirement. Since a fantastic CodeProject article already covers the SynchronizationContext in depth, a summary will only be described here.
SynchronizationContext Overview
-SynchronizationContext is an abstract base class for:
-WindowsFormsSynchronizationContext
-DispatcherSynchronizationContext
-WinForms automatically creates an instance of WindowsFomsSynchronizationContext for a WinForms UI thread.
-WPF automatically creates an instance of DispatcherSynchronizationContext for a WPF UI thread.
-Static property AsyncOperationManager.SynchronizationContext gets the SynchronizationContext for the calling thread: WindowsFormsSynchronizationContext in WinForms and DispatcherSynchronizationContext in WPF.
-SynchronizationContext provides two methods (Send and Post) for synchronous or asynchronous method invocation.
-Getting AsyncOperationManager.SynchronizationContext on a non-UI thread (such as in a console app or worker thread) returns the base SynchronizationContext class that just invokes a method on the calling thread when Send is used, and calls a method on a ThreadPool thread when Post is used.
Introducing the ThreadBarrier ‘Pattern’
The ThreadBarrier pattern consists of a non-UI class encapsulating and executing a worker thread and always raising its events on the UI thread using a SynchronizationContext. The worker thread execution should never leave the class via events. Data and objects within the class may be operated upon by the worker thread but all external events must be called on the UI thread. If this technique is correctly followed then the listening UI code does not need to worry about thread checks and delegate posting.
Implementing a ThreadBarrier’ (.NET 2.0)
A ThreadBarrier can be implemented as an abstract class so that derived classes automatically inherit the necessary functionality to support the pattern.
public abstract class ThreadBarrier
{
private SynchronizationContext _synchronizationContext;
protected ThreadBarrier()
{
this._synchronizationContext = AsyncOperationManager.SynchronizationContext;
}
The SyncronizationContext must be stored as a field so that it can be used by the worker thread in the derived class. Since the static AsyncOperationManager.SynchronizationContext property is called in the constructor, a critical requirement of properly using a ThreadBarrier derived class is that it must be created on the thread in which events want to be raised. In other words, create the class on the UI thread so that the UI’s context is captured. A worker thread can then use this context to post the methods that raise events back to the UI. The following protected method can be used for posting OnXXX methods:
public void Post<T>(Action<T> raiseEventMethod, T e)
where T : EventArgs
{
if (this._synchronizationContext == null)
{
ThreadPool.QueueUserWorkItem(delegate { raiseEventMethod(e); });
}
else
{
this._synchronizationContext.Post(delegate { raiseEventMethod(e); }, null);
}
}
For an event such as:
public event EventHandler Stopped;
protected virtual void OnStopped(EventArgs e)
{
if (this.Stopped != null)
{
this.Stopped(this, e);
}
}
A ThreadBarrier derived class simply posts events to the UI in this way:
Post(OnStopped, EventArgs.Empty);
A small example of a class implementing a ThreadBarrier would look like:
public class WeatherChecker : ThreadBarrier
{
public event EventHandler<WeatherEventArgs> TemperatureChanged;
public event EventHandler<WeatherEventArgs> HumidityChanged;
public event EventHandler<WeatherEventArgs> WindChanged;
public event EventHandler Stopped;
private bool _isStopRequested;
private Random _random;
public WeatherChecker()
{
this._isStopRequested = false;
//used for generating random weather information
this._random = new Random((int)DateTime.Now.Ticks);
}
public void Start()
{
Thread thread = new Thread(new ThreadStart(CheckWeather));
thread.IsBackground = true; //prevents thread from keeping app alive when app is closed
thread.Start();
}
private void CheckWeather()
{
while (this._isStopRequested == false)
{
Post(OnTemperatureChanged, new WeatherEventArgs(Rand(30f)));
Post(OnWindChanged, new WeatherEventArgs(Rand(15f)));
Post(OnHumidityChanged, new WeatherEventArgs(Rand(100f)));
//Updates roughly 4 times per second
Thread.Sleep(250);
}
Post(OnStopped, EventArgs.Empty);
}
public void RequestStop()
{
this._isStopRequested = true;
}
private float Rand(float max)
{
return (float)this._random.NextDouble() * max;
}
protected virtual void OnTemperatureChanged(WeatherEventArgs e)
{
if (this.TemperatureChanged != null)
{
this.TemperatureChanged(this, e);
}
}
protected virtual void OnHumidityChanged(WeatherEventArgs e)
{
if (this.HumidityChanged != null)
{
this.HumidityChanged(this, e);
}
}
protected virtual void OnWindChanged(WeatherEventArgs e)
{
if (this.WindChanged != null)
{
this.WindChanged(this, e);
}
}
protected virtual void OnStopped(EventArgs e)
{
if (this.Stopped != null)
{
this.Stopped(this, e);
}
}
}
The key points are:
-WeatherChecker derives from ThreadBarrier
-The worker thread is started inside this non-UI WeatherChecker class.
-The WeatherChecker should be created on the UI thread.
Now the UI code is very clean and does not have to worry about threads:
public partial class Form1 : Form
{
private WeatherChecker _weatherChecker;
public Form1()
{
InitializeComponent();
}
private void buttonStart_Click(object sender, EventArgs e)
{
this.buttonStart.Enabled = false;
this.buttonStop.Enabled = true;
this._weatherChecker = new WeatherChecker();
this._weatherChecker.TemperatureChanged += new EventHandler<WeatherEventArgs>(_weatherChecker_TemperatureChanged);
this._weatherChecker.HumidityChanged += new EventHandler<WeatherEventArgs>(_weatherChecker_HumidityChanged);
this._weatherChecker.WindChanged += new EventHandler<WeatherEventArgs>(_weatherChecker_WindChanged);
this._weatherChecker.Stopped += new EventHandler(_weatherChecker_Stopped);
this._weatherChecker.Start();
}
private void buttonStop_Click(object sender, EventArgs e)
{
this._weatherChecker.RequestStop();
}
void _weatherChecker_TemperatureChanged(object sender, WeatherEventArgs e)
{
this.labelTemperature.Text = String.Format(“{0:F1} C”, e.Value);
}
void _weatherChecker_HumidityChanged(object sender, WeatherEventArgs e)
{
this.labelHumidity.Text = String.Format(“{0:F0}%”, e.Value);
}
void _weatherChecker_WindChanged(object sender, WeatherEventArgs e)
{
this.labelWind.Text = String.Format(“{0:F0} mph”, e.Value);
}
void _weatherChecker_Stopped(object sender, EventArgs e)
{
this.buttonStop.Enabled = false;
this.buttonStart.Enabled = true;
this._weatherChecker = null;
}
}
Implementing a ThreadBarrier (.NET 3.5)
Since the requirement of deriving from a ThreadBarrier class is not ideal, in .NET 3.5 we can use extension methods to help us do the work.
public static class ThreadBarrierExtensions
{
public static void Post<T>(this SynchronizationContext synchronizationContext, Action<T> raiseEventMethod, T eventArgs)
where T : EventArgs
{
if (synchronizationContext == null)
{
ThreadPool.QueueUserWorkItem((e) => raiseEventMethod((T)e), eventArgs);
}
else
{
synchronizationContext.Post((e) => raiseEventMethod((T)e), eventArgs);
}
}
}
Now the WeatherChecker class does not have to derive from a ThreadBarrier (but it now has to do the work in its constructor of saving a reference to the SynchronizationContext, the task previously left for the abstract ThreadBarrier class).:
public class WeatherChecker
{
private SynchronizationContext _synchronizationContext;
…
public WeatherChecker()
{
this._synchronizationContext = AsyncOperationManager.SynchronizationContext;
…
}
Using an extension method on the SynchronizationContext allows us to post the OnXXX method:
this._synchronizationContext.Post(OnTemperatureChanged, new WeatherEventArgs(Rand(30f)));
Using Lambda expressions also simplifies the handling of the events in the UI code:
this._weatherChecker.TemperatureChanged += (weatherChecker, weatherEventArgs) =>
{
this.labelTemperature.Content = String.Format(“{0:F1} C”, weatherEventArgs.Value);
};
Conclusion
The ThreadBarrier technique is an easy way to simplify the use of a worker thread and how it interacts with the UI. It is not the silver bullet for multithreading, but it does offer a way to think of a thread as an ‘object’ and hide the threading complexities. The UI and worker thread can be separated even further by designing a ThreadBarrier class to do nothing but handle events from other threading code (such as in a library that you do not have the code) and propagate just these events to the UI. This truly allows the business ‘logic’ to be free of any UI dependencies.
Samples
UPDATE: The DelegateMarshaler has been revisited with a description of the 5 techniques in the sample.
July 24th, 2008 at 6:49 am
Hello:
While trying to automate an application using UI Automation framework (Microsoft), I am getting the automation tree broken. I am unable to get (trace) the controls on the application.
In some of the blogs i found the solution: run the code in the UI thread.
Can you please help me how to this.
Thanks,
Jitesh
July 24th, 2008 at 10:49 pm
You best bet is to try the MSDN forums: http://forums.microsoft.com/msdn/default.aspx?siteid=1