Silverlight 5’s New and Improved Threading Architecture
Silverlight 5 boasts a wealth of sexy new features, including a full-blown, hardware-accelerated 3D graphics API, support for custom markup extensions, multiple-window support in trusted out-of-browser apps, and the ability to run trusted applications in the browser (with sufficient permissions, of course). Post-beta, even more features will be added, such as support for invoking native operating system services using P/Invoke. But some of the most significant new features of the platform threaten to fly under the radar because they don’t lend themselves to glitzy demos. Among those features is a subtle but welcome change to Silverlight 5’s internal threading architecture.
That change is the introduction of a composition thread – a trick borrowed from Silverlight for Windows Phone. In Silverlight 4, the UI thread is responsible for virtually everything that matters: processing user input, rendering output, running animations, and more. As a result, long-running loops on the UI thread – something that developers must avoid at all costs – have debilitating effects. For example, if you’re running an animation and enter a 5-second for loop on the UI thread, the animation pauses for 5 seconds. That’s because Silverlight can’t do two things at once on a single thread, and while you’re executing code on the UI thread, Silverlight can’t use it to render changes to the visual tree, run animations, and so on.
Silverlight 5 changes this by offloading some of the work traditionally done by the UI thread to another thread called the composition thread. When present, the composition thread takes responsibility for managing operations that involve the GPU. For example, if an animation is running and IF the surface being animated is GPU-cached, then that animation will run entirely on the composition thread. The practical effect is that animations won’t stop if the UI thread is tied up performing other tasks.
It is important to note that the composition thread ONLY applies to GPU operations. If the object being animated isn’t GPU-cached – for instance, if you haven’t set that object’s CacheMode property to “BitmapCache” – then the composition thread doesn’t get involved. In fact, if you haven’t enabled GPU acceleration to begin with, there IS no composition thread. So to leverage the composition thread, you must 1) enable GPU acceleration in the application, and 2) make sure the objects you’re animating are handed off to the GPU. The first rasterization of those objects (and any rerasterizations resulting from changes to the objects’ visual properties) still takes place on the UI thread, but once that’s done, the composition thread takes over and drives any animations involving those objects. Unlike Silverlight for Windows Phone, Silverlight 5 doesn’t support GPU auto-caching, so you must be explicit about telling the run-time which parts of the visual tree to compose on the GPU.
While the addition of a composition thread doesn’t lend itself to eye-catching demos as the 3D graphics API does, there is a simple way to see the composition thread in action. The application pictured below uses three DoubleAnimations to continuously scale and rotate a Canvas containing a partially transparent penguin. Initially, because GPU acceleration isn’t enabled, the animations are driven by the UI thread. To demonstrate, click anywhere in the browser window while the animations are running. Doing so activates a MouseLeftButtonDown handler that calls Thread.Sleep() to put the UI thread to sleep for 2 seconds. When you click, the animations pause.
Now go into CompositionThreadDemoTestPage.html and change enableGpuAcceleration from false to true:
<param name="enableGpuAcceleration" value="true" />
Then run the application and click again. Voila! The penguin doesn’t stop animating. Why? Because the animations are now running on the composition thread. The penguin Canvas is GPU-cached because I already have its CacheMode property set to “BitmapCache.” And now that GPU acceleration is enabled, the composition thread automatically assumes responsibility for running the animations. If you tried the same experiment in Silverlight 4, the animations would pause despite the fact that the penguin is GPU-cached.
Does this mean that in Silverlight 5, it’s OK to execute long-running loops on the UI thread? Absolutely not. Non-GPU rendering is still performed on the UI thread, so if you make changes to the visual tree in a loop, those changes won’t be rendered until you exit the loop. But the composition thread allows animations to run more smoothly when the UI thread is busy doing other things, and it allows the application as a whole to be more responsive to user input by reducing the amount of work that the UI thread has to do. This is a significant step forward in the architecture of the run-time, and it brings Silverlight for the desktop up to par with Silverlight for Windows Phone as far as composition is concerned.