Page 2 of 5
The Invoke pattern
The pattern that tends to be used is to create additional threads which do the work of the application and provide access to the UI - after all worker threads have to present their results at some point - using the Invoke mechanism or to use an interthread communication mechanism to pass results to the UI thread directy.
Of the two the invoke pattern is the most often used because it requires the least amount of custom code. The Invoke pattern is described in detail in Deep Threading the gentle use of invoke but to put things simply a thread can call Invoke or BeginInvoke to pass a delegate to the UI thread to execute. That is the inter-thread communication is achieved by one thread asking the UI Thread to run a procedure of its choice complete with parameters.
Invoke is a blocking synchronous call and is much safer than BeginInvoke which is non-blocking asychrounous. Often Invoke and Begininvoke are presented as if they are safe options - they aren't.
The Invoke mechanism gives you the ability to ask the UI thread to run a delegate and so give it access to the UI components from a non-UI thread - but if that delegate accesses variables created by the worker thread then there is the usual potential for race hazards and deadlocks.
If you are using BeginInvoke then you are safe if you only use value types passed on the stack. Any attempt to access non-UI reference types or global types from within the invoked delegate is dangerous and you need to use locking.
In the case of Invoke the worker thread is at least suspended while the UI thread gets on with using what ever it wants. In this case the only problem arises if there are multiple worker threads sharing a set of variables. In the case of BeginInvoke you have to be very careful not to fall into the trap of allowing the UI thread and the worker thread .
So the Invoke pattern is fairly safe but BeginInvoke is as dangerous as full multi-threading.
The BackgroundWorker pattern
There is another problem with the Invoke method - its very messy. You have to create delegates, start and stop threads, locate the correct despatcher object and run Invoke or BeginInvoke. Its messy even before you get to worrying about synchronisation and locking.
The average developer may not understand threading well enough but they were brought up on event handling.
The BackgroundWorker pattern converts a problem in threading into one of event handling - although you can't completly ignore the fact that there are at least two threads in operation.
The BackgroundWorker class can be found in
and the key things you need to know about it is that it has three events:
When you use BackgroundWorker you usually define all three event handlers.
- The DoWork even handler is where you put the long computation.
- The ProgressChanged event handler is where you update the UI as the computation proceeds
- The RunWorkerCompleted event handler is where you can update the UI after the computation is complete.
If you have been following the disucssion you should be able to guess that the DoWork event handler runs via its own thread but the ProgressChanged and RunWorkerCompleted event handlers run on the UI thread as they have access the UI.
Although the ProgressChanged event handler sounds as if its role in life is simply to update a progress bar or some other progress indicator you can see that it can be put to a wider purpose.
The DoWork event handler can fire the ProgressChanged event anytime it wants to by calling the ReportProgress method. It can also pass to the ProgressChanged event handler a single integer which is supposed to indicate the percentage completed and an object which can contain any information it cares to package up. Clearly the ProgressChanged event handler can perform a general UI update and not just a progress indicator.
So to use the BackgroundWorker pattern you:
- Create an instance of the class.
- Define the DoWork event handler which runs in a non-UI thread.
- Define the ProgressChanged and RunWorkerCompleted event handlers to update the UI running on the UI thread.
- Arrange for DoWork to call ProgressChanged and RunWorkerCompleted at the correct times passing the correct data.
If you agree that every WPF/Silverlight application should be multi-threaded then what we now have is a blueprint for ever such application.
Let's try it out.