There was a query on the MSDN forums for WPF the other day that asked about leveraging multiple processors in WPF applications. I responded, and am basically repeating that response here, with a little bit of extension:
WPF 3.0 (and the version coming out with Orcas, 3.5) is definitely an STA(*)-based model, but here are a some observations/comments on how multiple processors can and should be used in WPF applications.
Serialized/Atomic programming model for UI programming is essential:
Since time immemorial (i.e. Windows 3.1 ) the way developers have written event-based UI programs has been fairly constant: register event handlers that are invoked through user interface actions, and have those event handlers modify the UI as well as maintain private state. There’s a fundamental assumption of, if not single-threadedness, at least atomicity and serialization of operations. STA definitely provides that. Rental-thread (**) could provide that as well, and does so in ASP.NET (but without any multicore benefits for a single application). Free threaded definitely doesn’t provide those guarantees and would result in havoc for UI developers.
In fact, we’ve seen such havoc in Windows Forms. Unlike WPF, where access from a separate thread to UI objects results in explicit exceptions, Windows Forms is looser. As a result, apps have gotten away, temporarily, with accessing UI objects from separate threads in Windows Forms. The problem is that it sometimes works, sometimes doesn’t. It’s one of the most widely reported programming problems with Windows Forms (and one of the motivations for making it more iron-clad in WPF).
Thus, we don’t believe that, as long as developers are writing stateful and state-modifying logic for manipulating their UIs (and there’s no sign of that changing), that making the event handlers be free threaded is the right way to go. However, there are alternative ways to use those cores.
WPF Implementation Use of Multithreading
There is a degree of multithreading support built into WPF, with the rendering/composition happening on a second thread from the UI thread. Thus there’s a natural two-thread division of labor. However, it doesn’t naturally extend beyond that.
While we don’t do so today, we’re definitely interested in subdividing internal computation across cores. Layout and databinding are both potential examples (though both also have potential pitfalls with invocation of stateful user code). Rendering can also potentially benefit from multiple cores. (Though not necessarily as much as one would initially think, since so much of the rendering is typically done on the GPU, which itself is massively parallelized.) Silverlight’s software renderer, for example, takes great advantage of multiple cores.
We’ll be looking into the above in the context of more broadly looking at performance wins that can be had.
Making application operations multithreaded
This leads to the final aspect here: None of the caveats above should prevent the application itself from being multithreaded. There’s great support in WPF and in the .NET Framework itself for doing so. The trick is to be sure that when it comes time to manipulate WPF objects, that that manipulation is done on the UI thread. Here are some techniques:
Use BackgroundWorker. This component invokes a “DoWork” event on a separate thread, and when that work is completed, raises a completion event back on the UI thread. Progress notification and cancellation are also supported.
The System.Net.WebClient class also provides support for this sort of asynchronous model, where file and data downloads occur asynchronously, but completion is reported back on the UI thread.
Web services (both Whidbey “web references” and Orcas “service references”) provide similar asynchronous support in the proxies that are automatically generated by VisualStudio. They provide methods that follow the general “Asynchronous Pattern For Components” model, which basically means that for a each synchronous web service call named Foo, there will also be a FooAsync() method and a FooCompleted event generated that provide asynchronous invocation of the web service. FooCompleted will be raised on the UI thread with the results of the web service invocation available in the EventArgs.
Use the WPF Dispatcher. The WPF Dispatcher can be used explicitly for finer-grained control of the message queue being processed by the WPF application. Other threads can post delegates to be invoked on the UI thread with a high degree of control.
Use multiple UI threads driving separate windows. A somewhat more advanced technique involves creating multiple UI threads each talking to its own Window. Each UI thread is STA, but if your application is suitably compartmentalized, this can be an effective technique.
Use multiple UI threads targeting the same window. A refinement of the technique above lets you create multiple UI threads, and bring their results together via HostVisual for hosting cross-thread visuals. Dwayne Need has a great blog post on doing just that.
Anyhow… hope this helps with some context and ideas. We think this opens lots of doors for creative use of multiprocessing within WPF applications. Interested in your experiences with all of this stuff!
(*) STA stands for “Single Threaded Apartment”, which basically means that only the thread that creates a UI object is able to manipulate (i.e., get or set properties, or call methods) it.
(**) Rental-threaded, or RTA (for “Rental Threaded Apartment”), means that any thread can take ownership and manipulate a UI object, but only one can do so at a time. So operations are guaranteed to be serialized, even if they execute on different threads.