Threading in the Windows Runtime: Part 1
During the Build 2013 conference Marytn Lovell revealed some of the inner workings of the WinRT threading model. While .NET developers may be surprised at its complexity, especially when multiple windows are involved, traditional COM developers are going to be relieved to learn that it is much simpler than what they are used to.
The key points that Marytn wants developers to understand about WinRT multithreading are
- Use Async for long running operations
- Use agile objects for everything except UI objects
- Don’t block the UI thread.
Threading from the Application’s Perspective
The application basically sees the world as having UI threads and non-UI threads. UI objects “live” on a particular UI thread, while most other objects can be used on any thread. This is a major change from the desktop COM design, which presents the user with a wide variety of thread types and threading models.
Another difference between WinRT and COM is object lifetime. With the exception of UI objects, you can create objects on any thread, use them on any thread, and, if using C++, destroy them on any thread. By contrast, COM objects are bound to the thread that creates them. If you wish to move them to another thread special calls have to be invoked.
Why are UI Objects Special?
Back around 2000 Microsoft created a multithreaded UI framework where UI objects were not special and could be created and used on any thread. From a computer science perspective this sounded like a great idea, but when it came to actually building programs it resulted in some serious issues. For example, imagine the user presses the Ok button and then, while the Ok_Clicked event is being processed, the user pressed the Cancel button. You don’t want Ok_Clicked and Cancel_Clicked running at the same time, but you don’t want to writing locking code in each of the event handlers to block the other. In short, UI interactions are naturally serialized.
Multiple UI Threads
It is tempting to think that an application only has one UI thread, that being its Main or entry-point thread. In actuality it can have multiple UI threads handling different purposes. In WinRT 8, you will have a separate UI thread spawned to handle the Contracts (e.g. share) UI.
WinRT 8.1 adds the ability to show multiple views, each in its own Window. Each of these views will get its own UI thread.
It should be noted that the Main UI thread will always exist for the lifetime of the application, even if it isn’t being used to display a UI. This can occur if the application was launched just to show a Share dialog. So if you have code that needs to live on a single thread, you should still put it on Main.
In desktop COM, there is the concept of reentrancy. When you make an outgoing COM call, another incoming call can come in and take over the thread. This gives the illusion of two threads running on the UI concurrently, which is usually not what you want to happen. What’s worse is that it isn’t always obvious that you’ve made an outgoing COM call that pumps messages.
Visual Basic 6 and WinForms programmers may remember the problems caused by the DoEvents function, which temporarily made the UI responsive during a lengthy event handler by pumping messages. This reentrancy behavior is like scattering those DoEvents calls throughout your code with the hope that they don’t actually cause anything to happen. And it often can’t be avoided because COM objects need their messages to be pumped in order to operate.
In this first slide you can see how a second call can interrupt the first one when message pumping occurs.
A change made in WinRT 8.1 is the elimination of unlimited reentrancy. Only “logically connected threads” are going to be allowed to make calls back to the UI thread. All other messages are going to be blocked until the current outgoing call has completed.
In this second slide we see call 2 not being dispatched until call 1 completes.
Deadlocks in ASTA Threads
With the introduction of multiple window support in WinRT 8.1, the possibility of multiple UI threads was also introduced. The specter of deadlocks occurs when the two threads make outgoing calls each other at the same time. Since each is blocked waiting for its call to return, we have a potential deadlock.
Since this scenario requires very tight timing that is hard to reproduce, catching it during a debugging session would be next to impossible. So Microsoft has chosen to go a different route. When a potentially deadlocking call is invoked, it fails immediately. It doesn’t wait for the deadlock to actually occur, but rather ensures the bad code fails 100% of the time. In short, one ASTA thread (a.k.a. UI thread) cannot call another.
That isn’t to say cross-view communication isn’t possible. One view can send messages to another view using its dispatcher. Or for a more sophisticated design an async object can be created that lives in the thread pool.
OS Enforced Threading Restrictions
Desktop applications don’t have to respond to the periodic messages from the operating system that ask whether or not it is still alive. They should, because being unresponsive is a bad thing from the user’s perspective, and the OS will flag the application by graying it out and tagging it in the Task Manager.
In the WinRT world the rules are far more restrictive. If an application becomes unresponsive for too long because the UI thread is occupied the application will be terminated. Thus it is vital that applications use background tasks for long running operations. This usually means using UI safe primitives (below) instead of instead of locking primitives such as a mutex or semaphore.
- C++: create_task
- VB/C#: await
- JS: promise
Another common primitive that must be avoided is Task.Wait. In C++, calling wait on a task from the UI thread will immediately trigger an error. That protection hasn’t made its way into .NET yet, but given the number of applications that are terminated due to that mistake it may happen in the future.
Threading by Environment
A useful feature of WinJS is that callbacks and events always return to the source thread. Contrast this with .NET, where without the async keyword events need to be manually marshaled back onto the UI thread.
In the XAML environment (C++ or .NET), all XAML objects are thread bound. Furthermore, all XAML objects in a given visual tree must be hosted on a single thread. This means you cannot create a control on one view and then move it to another view in a different window.
By contrast, this wasn’t a restriction in the Win32 world. Under that model an hwnd (window handle) could be the parent of another hwnd on a separate thread.
Marytn didn’t go into detail about the DirectX model, other than to say that it is similar to XAML with a few additional details.
The material in this article was originally presented in the Build 2013 session titled Windows Runtime Internals: Understanding the Threading Model. InfoQ’s summary of the key points will continue tomorrow.
Tom Gilb & Kai Gilb Jan 26, 2015