I managed to write a relatively large WinForms application in C# that functions correctly without the [STAThread]
attribute on the Main()
method.
To accomplish this I had to override a lot of WinForms functionality (such as using a custom BeginInvoke
and Invoke
functions), use a custom message loop instead of Application.Run
, use a custom file dialog instead of OpenFileDialog
and SaveFileDialog
, and use WM_DROPFILES for drag-and-drop instead of the WinForms out-of-the-box OLE approach. This is all "for science".
Now I want to test for any possible performance impact of omitting the STAThreadAttribute from all GUI threads. I do not possess deep enough knowledge of the COM configuration used internally by the Control class to be able to predict such impact. Speed of execution probably depends on which thread is calling the internal COM object of the Control
.
Admittedly, I am having trouble coming up with a benchmark that would test for performance impact pertaining to [STAThread]
, because I am unsure of which functions/operations would be affected by such a change (specifically related to the Control
class).
What should I look for exactly? Which operations/methods from the Control
class should I expect to run faster/slower by omitting [STAThread]
, if any?
Addendum: The rationale is that I am slowly migrating my application to use a custom windowing system (for portability reasons, mainly for using Mono on Linux, whose WinForms implementation is not complete), so I had to override a lot of functionality myself anyway. It was merely a coincidence that I noticed that I had overridden so much functionality that I could omit [STAThread] and everything would still work as expected.
I expect a change in performance due to COM marshaling calls from the ThreadPool
(which is configured as MTA), and the GUI thread (which by default should be configured as STA). Calls from the ThreadPool
to the GUI thread would need to be marshalled due to them being configured in different thread apartments, which introduces a synchronization overhead. By leaving the GUI thread as MTA, the marshalling should be reduced, hence possibly faster execution of function calls. I would like to test this claim pragmatically.
[STAThread] has too much mystique attached. But in practice is very simple, you make a promise. Cross your heart, hope to die. You promise to the OS that your main thread is a hospitable home for code that is not thread-safe. Keeping the promise requires having a dispatcher (Application.Run) and never blocking the thread. Things you do later, that's why you have to make the promise up front.
In a GUI app that runs on Windows there is always a lot of that code around. Whatever framework you use is the more obvious place for such code. But the far nastier stuff is the code you cannot see. The kind that lives inside shell extensions, in UI Automation code, in apps that want to provide data through the clipboard or drag+drop, in hooks installed with SetWindowsHookEx, in screen-readers for users with visual impairments, in ActiveX components that expect PostMessage to work. Such code doesn't have to be thread-safe, the OS does not demand that it is, mostly because whomever wrote that code has no way to test it. He didn't know beans about your app.
It matters to the OS because it has to do something when such code does not run on the UI thread. Since the code explicitly announces that it is not thread-safe, or doesn't have to be, it has to keep the code safe anyway. Only way that is possible is by initializing the code and make any future calls to it on the same thread. That requires some trickery, the code has to be marshalled, made to run on another thread, having a dispatcher loop is crucial to make that work. A dispatcher is the universal solution to the producer-consumer problem.
But if the code is initialized from the right thread then this is not necessary. How does the OS know whether it is the "right thread"? The [STAThread] promise tells it that.
So first conclusion you can make is not marking the UI thread as STA actually makes your program slower. Since every call is marshalled. Not the only problem, there is a lot of such code that cannot be marshalled. The author has to do extra work to enable it, he has to provide a proxy/stub. Sometimes it is just too hard, often he simply doesn't since he relies on you doing it right. So the call will fail miserably. But this happens in external code, you won't find out. So stuff just doesn't work. Or deadlocks. Or an expected event doesn't get raised. Miserable stuff. You must use [STAThread] to avoid the misery.
This is otherwise a pure Windows implementation detail, it does not exist on the Unixes. They have just completely different ways to provide these features, if at all. So [STAThread] does not mean anything on such an OS and testing what happens without it doesn't tell you anything.