Threading in the Windows Runtime: Part 2
The information in part 2 of Threading in the Windows Runtime deals with the internals of the threading model. This section, originally presented by Marytn Lovell at Build 2013, is intended to be trivia or possible useful in debugging, but not necessary for day to day development. For more practical information, please refer to part one of InfoQ’s key points summary.
Marshalling is the COM term for allowing an object to be used from another thread or process. Most WinRT objects don’t require marshalling and the ones that do are generally designed so that the developer never sees the marshalling code. An example of this is when using the FileOpen dialog, which marshalls a call to the System Broker, a separate OS component.
An interesting difference between desktop COM and WinRT is the way that proxies are “agile” in WinRT. By that we mean the proxy to the external object (e.g. the FileOpen dialog) can be freely moved from thread to thread without marshalling. Calls from the proxy to the real object are of course marshalled.
The interfaces IMarshal and INoMarshal are used to indicate whether or not the object wants to participate in marshalling. WinRT objects generally op out of marshalling as they are usually either UI objects or agile objects. UI objects can only be called by the UI thread, so any marshalling is done via the Dispatcher rather than at the object level. And again, agile objects don’t need to be marshalled by definition.
Please note that thread-safety and agility are unrelated concepts. While an agile object can be called from any thread, that doesn’t mean it is safe to concurrently call it from multiple threads.
In the rare cases where marshalling is required, the operating system will generate the correct code based on the MIDL-generated data.
Thread Pool Threads
Threads created in the WinRT runtime are always initialized as a WinRT thread unless created as a UI thread. This avoids the myriad of thread creation options found in the desktop COM world such as CoInitializeEx.
Async operations that are initialized on a UI thread will be protected back onto that UI thread when the operation is complete. But this isn’t a runtime feature, it is something requested by the programming languages via the async primitives (create_task, await, promise).
Async operations that are initialized on a thread pool thread will be completed on whatever thread was grabbed by the OS to perform the async work or deliver the async complete message.
As mentioned earlier, agile objects are objects that can be freely moved from one thread to another without marshalling. These are “normal” objects in C++ and C#. Agile objects can only store other agile objects. If you want to store a raw IUnknown or IInspectable and you are uncertain if the underlying object is also agile then you have to wrap it in an agile reference.
Agile objects are easier to deal with than non-agile objects in part because they don’t “die” when their host thread dies. In desktop COM you need to keep an object’s thread around in order to perform tasks such as pumping messages.
If you want to know at runtime if an object is agile, check for the IAgileObject interface. If it doesn’t implement it you can use the RoGetAgileReference function to get an IAgileReference to it. This function is new in WinRT 8.1, previously you need to work with the global interface table.
Apartments are a COM concept around how thread and object lifetimes are managed. Understanding apartments was crucial when using 90’s desktop programming languages. Even Visual Basic 6, which was essentially single-threaded, required a basic understanding of COM’s apartment models.
Marytn Lovell also describes them as,
Apartments are a COM concept that makes trouble and makes annoying things happen at annoying times in ways you didn’t expect.
One of the key goals of WinRT was to make it so that you didn’t need to know that apartments even exist. That said, they do and if you are curious there are the three types supported by WinRT applications.
- ASTA – Application Single Threaded Apartment, UI Threads
- MTA – Multi-threaded Apartment, Thread Pool Threads
- NTA – Neutral-threaded Apartment, Helpers for inter-process calls
Object Lifetime and Threads
Marytn keeps bringing this topic up over and over again because he is proud that those problems largely don’t exist in WinRT. As mentioned before, agile objects allow the developer to control the object’s lifecycle instead of the apartment/thread. Pretty much the only time you have to worry about disconnected proxies now is if someone manually terminates a process such as the System Broker.
When delivering messages to a thread, there is often contention about which should be delivered and in what order. For example, key presses can occur while the mouse click handler is still running. The design in Windows 8 can result in lost keystrokes when the input queue is blocked. This has been fixed, or at least improved, in Windows 8.1 so that the input queue isn’t blocked and events are not discarded by COM. Work items can also be prioritized in the scheduler used by Windows 8.1.
The material in this article was originally presented in the Build 2013 session titled Windows Runtime Internals: Understanding the Threading Model.