The Component Object Model (COM) has this thing called apartments. What are they?
I’m going to explain them by going into the history.
In the beginning, we had 16-bit Windows. Each process had only one thread. Code took advantage of this by assuming single-threaded operation.
If you wanted to make a call from one process to another, the call was marshaled: The parameters to the call were serialized into a memory buffer, that memory buffer was sent to the receiving process, which deserialized them back into the parameters. This works out fine for parameters that are pass-by-value, like integers and strings.
For parameters that are pass-by-reference, deserializing the object isn’t enough. If somebody makes a mutating method call, that mutating method call must go back to the original object, not to a copy. The way COM did this was to create a fake object on the recipient side (a proxy). If the recipient of the call invoked a method on the proxy, we recursively marshaled a call back to the original process.
The idea behind marshaling was that you can treat every object as if it were local. If the object happens not to be local, the proxy steps in and forwards the call to the actual object in some other process.
Okay, so far we have single-threaded processes and marshaling between processes.
Windows NT introduced multithreading. Now processes could have multiple threads. But there are all these components that assumed single-threaded operation. The solution was to treat each thread as if it were its own process. Communicating between threads was done the same way as communicating between processes: Calls between threads and cross-thread object references are marshaled.
Each “single-threaded pseudo-process” was called an apartment.
A little while later, COM introduced the concept of the multi-threaded apartment (MTA), and the old-timey “single-threaded pseudo-process” apartments were renamed single-threaded apartments (STA).¹ The multi-threaded apartment is one in which all the threads can share objects freely among each other without requiring marshaling.² Of course, in order for this to work, the objects themselves must support multi-threaded operation, which usually means lots of mutexes to protect internal state. (And lots of mutexes create the opportunity for lots of deadlocks.)
Every process that uses COM consists of zero or more single-threaded apartments, plus exactly one multi-threaded apartment. The multi-threaded apartment consists of every thread that isn’t in a single-threaded apartment. This could happen because the thread explicitly asked to be part of the multi-threaded apartment (by passing the COINIT_
MULTIÂTHREADED
flag to CoÂInitializeÂEx
), or because the thread never expressed any opinion and merely defaulted to the multi-threaded apartment, a situation which is known as being part of the “implicit multi-threaded apartment“.
In general, writing single-threaded objects is easier because you don’t have to deal with all the race conditions inherent in multi-threaded programming. Multi-threaded objects will typically use locks, and waiting for a lock from a UI thread is a great way to make your UI appear to hang. Furthermore, UI operations are single-threaded, so any component that displays UI or interacts with user interface objects will necessarily be single-threaded.
Windows 2000 introduced the concept of the neutral-threaded apartment (NTA). This apartment doesn’t have a dedicated thread. Instead, when a call is made into a neutral-threaded object, the current thread is temporary commandeered by the neutral apartment for the duration of the call. Neutral-threaded apartments were cool back in the day, but you don’t see them much any more, and most people have forgotten that they even exist. Let us not talk about the neutral-threaded apartment again.
Windows 8 introduced a variant of the single-threaded apartment known as the application single-threaded apartment (ASTA). It’s basically the same as the STA, except that it blocks reentrancy to avoid certain categories of reentrancy bugs and deadlocks.
So that’s the quick introduction to apartments. An apartment is a thread or group of threads that can share objects freely among each other. As far as COM is concerned, all threads in an apartment are equivalent. Things get interesting only if you need to communicate between apartments.
Now that I gave you a crash course in apartments, I’m going to confuse matters by introducing contexts. Next time.
¹ There is some optimization opportunity available if the marshaling is within a process. For example, pointers can be marshaled by simply copying them.
² The C++ language took a different approach: Objects can be shared freely among threads, provided that only one thread performs a non-const operation at a time, and no non-const operation can occur at the same time as a const operation. This basically allows the component to abdicate responsibility for the problem and instead puts the responsibility on the callers.
Applying this rule to the COM world is difficult because it is common for a single component to be referenced by multiple other components, none of which know about each other. For example, there could be multiple clients all with a reference to the same Excel spreadsheet. One add-in is inspecting the data for inconsistencies. Another is cleaning up the data by removing leading and trailing spaces. Another client is monitoring the spreadsheet for changes. And a final client is manipulating data inside the spreadsheet as part of a complex custom calculation. These components have no way of coordinating their actions since they don’t know about each other. The only thing they have in common is the Excel spreadsheet itself.
Best explanation of apartments I’ve ever read.
This is great. I never quite wrapped my head around apartments, and .NET does a good job of abstracting it away.
I couldn’t understand why objects had to be marshalled to the UI thread — couldn’t the UI thread just access the memory directly? Now it makes sense.
Good summary! Besides being a clear, short description of COM apartments, it also reminds us why COM can be a big source of bugs. It is hard enough for a programmer to deal with threads when the project contains only their own code. When you have objects that interact over several threads, for most of which you have poor documentation and no source code, bugs will raise their ugly heads.
Au contraire, STAs abstract syncing so much that for most of the usual multi-threaded scenarios you just call methods across threads with no syncing code at all.
“Objects that interact over several threads” might be a problem in MTA but you have to know what you are doing if STAs are not enough for you “screaming performance” threading code.
“… you have to know what you are doing …”
You can always say X is not a problem if you know what you’re doing. My point is that with objects interacting with other objects made by unknown programmers working for other companies, we can’t count on them knowing what they are doing yet our code is often at their code’s mercy.
I am of the extremist fringe opinion that threads are dangerous, evil, and should never be used. There may be more overhead in IPC as opposed to sharing memory between threads, but it’s vastly safer from many classes of serious bugs.
Alex what programming language do you use? The way C# and .NET team up to handle structured multithreading should be taught as the way to do it for every language because the patterns are simple and solid and all the terrifying boilerplate is finally gone. It's just you and your algorithm now. So much of the Win32 API was so far ahead of its time but I never saw it clearly for 25 years because...
Oh God no. Whatever anyone does don’t take .NET as the gold standard for multi threading.
Asynchronous code is something very, very different to multi threaded code and one should do well to remember that.
But if you really think that .NET has a sane multi threaded standard quick tell me the guarantees given by volatile and whether they differ for different platforms (x86 vs. ARM).
I am all for async. I just don’t like preemptive/multicore shared-memory threads because of the seemingly unlimited number of bugs that arise the moment they enter the picture. They just aren’t safe, even if they do provide a performance boost.
Where I work (it's certainly a company you've heard of; you might even use our products) this sort of laughable naivete wouldn't make it past the interview stage.
For instance, you couldn't possibly build a scalable relational or non-relational database product using IPC. It's not possible. You need dozens, perhaps hundreds of threads that have access to precisely the same data at a moment's notice. The kind of synchronization work required to ensure...
Maybe I should clarify. I'm not against shared memory as a concept. I'm opposed to shared memory by default. Shared memory should have to be explicitly set up with appropriate locking. Modern threading models mostly just throw a bunch of threads in the same address space and hope the programmer is skilled enough to avoid synchronization bugs, which is usually not the case, and make all memory shared. That's what I consider dangerous.