Most desktop applications (both with WinForms and WPF) have to cope with thread dispatching.
Indeed, the graphical subsystem of Windows (on the top of which both .NET WinForms and WPF are built) is intrinsically single-threaded. It is based on a message queue where individual actions (processing a button click or rendering the dialog box) are executed one after the other. So when an event handler for a button executes, the progress bar cannot be rendered, even if its value has been updated. Neither can the user interface react properly to a click of the Cancel button.
Consequently, a golden rule for graphical programming is to never do anything long in the GUI thread.
The commonly used solution is to execute long operations in a worker thread. In the .NET Framework, it is generally considered best practice not to create a new thread for every operation, but instead, to queue a work item into the thread pool.
The following code handles clicks by using the Save button. It queues the I/O operation for asynchronous execution on a worker thread.
private void okButton_Click(object sender, RoutedEventArgs e) { ThreadPool.QueueUserWorkItem( delegate { this.contact.Save(); }); }
Now, what if we want to display a message after the contact has been saved? Since the graphical subsystem is single-thread, we cannot invoke the MessageBox.Show method from the worker thread. Therefore, we have to dispatch it to the GUI thread. With WPF, we have to use a dispatcher object, as demonstrated in the following code sample:
private void okButton_Click( object sender, RoutedEventArgs e ) { ThreadPool.QueueUserWorkItem( delegate { this.contact.Save(); this.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new Action( () => MessageBox.Show( GetWindow( this ), "Saved!" ) ) ); } ); }
The code looks very similar with WinForms: instead of this.Dispatcher.BeginInvoke,
we have to invoke this.BeginInvoke.
As you can see, multithreading quickly makes the code unreadable and error-prone. Fortunately, there's a better solution. What if we could mark the affinity of methods directly to the worker thread, or GUI thread, and eliminate the plumbing code? It seems unrealistic, doesn't it? But with PostSharp and aspect-oriented programming, it's not unrealistic at all.
So let’s dream on. What we want are two custom attributes: the OnWorkerThreadAttribute,
when the method should be executed asynchronously on a worker thread; and the
OnGuiThreadAttribute, when the method should be executed on the GUI thread.
The code would look like this:
[OnWorkerThread] private void okButton_Click( object sender, RoutedEventArgs e ) { this.contact.Save(); this.ShowMessage( "Contact Saved!" ); } [OnGuiThread] void ShowMessage(string message) { MessageBox.Show(GetWindow(this), message); }
Both custom attributes will be derived from the class MethodInterceptionAspect.
They will intercept calls to the method to which they are applied. Our first custom
attribute, OnWorkerThreadAttribute, is trivial:
[Serializable] public class OnWorkerThreadAttribute : MethodInterceptionAspect { public override void OnInvoke( MethodInterceptionArgs args ) { ThreadPool.QueueUserWorkItem( state => args.Proceed() ); } }
In this attribute, we implemented the method OnInvoke, which will be
invoked instead of the intercepted method.
The statement args.Proceed() then proceeds with the invocation of the
intercepted method. As you can see, the implementation of this aspect simply queues
the execution of the intercepted method into the thread pool.
The implementation of the OnGuiThreadAttribute is a little more complex.
WPF and WinForms implementations are very similar. In both cases, we first need
to check if we are already on the GUI thread (using Dispatcher.CheckAccess
on WPF or InvokeRequired on WinForms). If we are not, we need to invoke
the intercepted method through Dispatcher.Invoke or Invoke,
respectively.
The following code is a complete aspect implementation for WPF:
[Serializable] public class OnGuiThreadAttribute : MethodInterceptionAspect { public DispatcherPriority Priority { get; set; } public override void OnInvoke( MethodInterceptionArgs eventArgs) { DispatcherObject dispatcherObject = (DispatcherObject)eventArgs.Instance; if (dispatcherObject.CheckAccess()) { // We are already in the GUI thread. Proceed. eventArgs.Proceed(); } else { // Invoke the target method synchronously. dispatcherObject.Dispatcher.Invoke(this.Priority, new Action(eventArgs.Proceed)); } } }
These two simple custom attributes can have a significant impact on the way you think about multithreading. You can now forget the thread pool and the WPF/WinForms message dispatcher. All you have to think about is where the method should be executed: can it run asynchronously on a worked thread, or does it require a GUI thread? Using these two simple aspects makes your code easier to read and less error-prone.