Reputation: 374
I'm working with WPF and am reading through materials on STA. Two questions:
1) STA is defined as standing for both "single thread affinity" and "single threaded apartment" by different articles. This says the former: https://msdn.microsoft.com/en-us/library/ms750441(v=vs.110).aspx This says the latter: https://msdn.microsoft.com/en-us/library/ms742522(v=vs.110).aspx
Which is it, or can STA refer to either of these related concepts?
2) Is it correct to describe their relationship as: Single threaded apartment is a model used by various components of Windows, thread affinity is a characteristic of that model?
Thanks.
Upvotes: 4
Views: 3143
Reputation: 2165
Upvotes: 2
Reputation: 8135
The STAThreadAttribute
tells the .NET virtual machine to setup the initial thread's COM apartment to Single-Threaded Apartment (STA).
1) This is what STA means, A is for apartment, not affinity.
The main issue here is when dealing with (external) COM components, usually GUI controls, such as the WebBrowser
control that instantiates IE's embedded component, which must live an STA.
2) Yes, the STA implies thread-affinity, which is a requirement for Win32 windows and window messages in general.
In practice, you can either mark your Main()
with [STAThread]
or create a new thread, set its apartment and then start it, as long as you run a message loop later on, such as Application.Run()
, or WaitOne
on a WaitHandle
, since it'll use CoWaitForMultipleHandles
.
So, here are the implications of the STAThread
attribute or starting a thread set up for STA:
The virtual machine will call CoInitializeEx
with COINIT_APARTMENTTHREADED
You must run a message loop to ensure cross-apartment calls to the apartment's objects
Even if you don't expect cross-apartment calls, you should still run a message loop, because STA COM components may require one, usually due to the use of GUI APIs, directly or indirectly e.g. shell functions
WaitOne
and WaitAny
will use a message pumping wait, such as CoWaitForMultipleHandles
instead of a plain WaitForSingleObjectEx
/WaitForMultipleObjectsEx
WaitAll
is not supported, because the underlying API would wait on all handles and the message queue, there's no passive way to wait on all handles of a set of handles or the message queue
Upvotes: 4
Reputation: 942177
STA means "Single Threaded Apartment". It is a COM concept, highly unrelated to WPF. The WPF designers learned from the mistakes in .NET 1.x, many programmers wrote programs that violated thread-safety requirements and had a very difficult time fixing their programs. So they added more runtime checks in .NET framework code to help them keep out of trouble.
While it is pure COM concept, the underlying principle is pretty universal. By declaring a thread STA, you make a promise that the code you write is well-behaved. You write well-behaved code by running a dispatcher loop (Application.Run) and never block code that runs on the thread. Breaking this promise causes deadlock.
The vast majority of .NET classes, as well as most of the code you write yourself, is thread-unsafe. There are two basic ways to make such code thread-safe. The first one is the approach you take with a class like List<>, you put lock statements in your code to ensure that the List object can only be accessed by a single thread at a time.
That usually works fine, but the more elaborate the class gets, the harder it gets to figure out where to put the lock statements and the higher the odds that such locks cause deadlock. The only other option you then have to keep the code thread-safe is to only ever make method calls on the same thread. All of the WPF components are like that, you must call Dispatcher.Begin/Invoke() from a worker thread to set their properties or call their methods. You can now say that the object has thread-affinity, it is only usable from the thread that created it.
The WPF designers wanted to add those runtime checks to tell the programmer he's using the WPF component wrong. The component can only work on a thread that called Application.Run() so that Dispatcher.Begin/Invoke() is going to work. Problem is, they can't tell whether the thread is going to call Application.Run(). That often happens later. A promise is needed.
So they borrowed the COM promise, the apartment state of a thread is always set and gives a decent hint how it is going to behave. No guarantee, just a decent hint. The main thread of a WPF app is almost always appropriate, it is STA thanks to the [STAThread] attribute on the Main() entrypoint and the project template ensures it calls Application.Run(). And a Task or thread-pool thread is in the MTA and will never call Application.Run() so is a very hostile place for a WPF component.
The exception they generate keeps the programmer out of trouble. Note how you can very easily suppress this exception. All you have to do is call Thread.SetApartmentState() to make the promise. But of course you are now flying without the safety net, WPF can no longer tell you that you are doing it wrong, it is now entirely up to you to write the code correctly. You must call Application.Run(), even though you most typically do not want to do that.
Upvotes: 15