Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ApartmentState for dummies

COM is the grand father of .NET. They had pretty lofty goals with it, one of the things that COM does but .NET completely skips is providing threading guarantees for a class. A COM class can publish what kind of threading requirements it has. And the COM infrastructure makes sure those requirements are met.

This is completely absent in .NET. You can use a Queue<> object for example in multiple threads but if you don't lock properly, you'll have a nasty bug in your code that is very hard to diagnose.

The exact details of COM threading are too large to fit in a post. I'll focus on the specifics of your question. A thread that creates COM objects has to tell COM what kind of support it wants to give to COM classes that have restricted threading options. The vast majority of those classes only support so-called Apartment threading, their interface methods can only safely be called from the same thread that created the instance. In other words, they announce "I don't support threading whatsoever, please take care of never calling me from the wrong thread". Even if the client code actually does call it from another thread.

There are two kinds, STA (Single Threaded Apartment) and MTA. It is specified in the CoInitializeEx() call, a function that must be called by any thread that does anything with COM. The CLR makes that call automatically whenever it starts a thread. For the main startup thread of your program, it gets the value to pass from the [STAThread] or [MTAThread] attribute on your Main() method. Default is MTA. For threads that you create yourself it is determined by your call to SetApartmentState(). Default is MTA. Threadpool threads are always MTA, that cannot be changed.

There's lots of code in Windows that requires an STA. Notable examples you'd use yourself are the Clipboard, Drag + Drop and the shell dialogs (like OpenFileDialog). And a lot of code that you cannot see, like UI Automation programs and hooks to observe messages. None of that code has to be thread-safe, its author would have a very difficult time making it safe without knowing in which program it gets used. Accordingly the UI thread of a WPF or Windows Forms project must always be STA to support such code, as does any thread that creates a window.

The promise you make to COM that your thread is STA however does require you to follow the single-thread apartment contract. They are pretty stiff and you can get tricky to diagnose trouble when you break the contract. Requirements are that you never block the thread for any amount of time and that you pump a message loop. The latter requirement is met by a WPF or Winforms' UI thread but you will need to take care of it yourself if you create your own STA thread. The common diagnostic for breaking the contract is deadlock.

There's quite a bit of support built-in the CLR to support these requirements btw, helping you to keep out of trouble. The lock statement and WaitOne() methods pump a message loop when it blocks on an STA thread. This however only takes care of the never-block requirement, you still need to create your own message loop. Application.Run() in both WPF and Winforms.

I've previously contributed an answer that contains more details about the significance of having a message loop to keep COM happy. You'll find the post here.