4

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.

user603563
  • 374
  • 3
  • 15

3 Answers3

15

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    There is no difference, in terms of COM, between stating `[STAThread]` for the `Main()` thread, and starting a thread that was `SetApartmentState(STA)`, the execution contract is exactly the same: an STA requires message pumping to ensure cross-apartment calls to COM objects in this STA are processed. You can have WPF on a non-`Main()` thread without trouble. Also, `CoInitializeEx` is called with a different argument by the virtual machine, which is just as important as running a message loop. – acelent May 26 '15 at 14:42
  • Hans, just to clarify/summarise: STA is a COM concept that WPF was required to implement (by having a dispatcher which manages thread affinity) in order to interact with COM components. Is it true to say "WPF implements STA." or "WPF follows the STA model." ? – user603563 May 26 '15 at 18:51
  • No. WPF can't completely ignore COM but it tries pretty hard. How it *borrowed* the concept is the important part. Quote: "the underlying principle is pretty universal". That principle is the standard solution to the [producer-consumer problem](https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem). COM needs that, WPF needs that. The WPF dispatcher loop is that solution. – Hans Passant May 26 '15 at 19:01
  • That is quite misleading. Nothing in a producer-consumer system requires thread-affinity. You can have a combination of multiple producers, multiple consumers, and having work done in worker threads that don't ever care if they're producing or consuming at each work item. But in reality, [WPF "solves" its problems with thread-affinity, because its problems are Win32 message queues, Win32 controls and STA COM components.](https://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx) Had the underlying UI and object model not require it, most probably WPF wouldn't either. – acelent May 26 '15 at 21:30
  • No, there is just one consumer, the class library that's fundamentally thread-unsafe. Being fundamentally thread-unsafe without a practical way to put *lock* in the right place requires solving the producer-consumer problem. Where-ever the producers come from, whether from a worker thread that calls Dispatcher.Begin/Invoke() or the operating system or other programs sending notifications. – Hans Passant May 26 '15 at 21:54
3

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

acelent
  • 7,965
  • 21
  • 39
  • Hi, thanks for your help - I think needed both answers to help me here, but as Hans answered first I'll mark his as correct. – user603563 May 28 '15 at 20:28
2
  1. STA stands for "single threaded apartment" - COM model for handling multithreaded access to COM object . Thread affinity is an implication from this model - once one thread entered STA apartment no other thread can enter this apartment. And the thread owner of apartment cannot be changed.
  2. It is correct to describe their relationship as: Single threaded apartment is a model used to handle multithreaded access to COM object, thread affinity is a characteristic (implication) of that model.
Vlad
  • 1,977
  • 19
  • 44