STA call from MTA

2.6k views Asked by At

I am just starting to deal with STA/MTA issues, so apologies for the simplicity of the question. I couldn't find an answer I could actually understand at the bottom of the ladder here.

I am writing a plugin for another piece of software, and come to a point in a worker thread that I need to create some UI elements. I understand that I cannot do that from inside the worker thread since it is not an STA thread, and that I need to get back to the Main (or just another?) STA thread to create the UI elements. Some clarifications would help greatly.

  1. Do all STA threads have the same 'rights', i.e. if the main thread is STA and creates a Window, adds some UI elements to it. Then spawns off another STA thread, and that second thread likewise creates some UI elements, are they doing it in the same 'space' (poor word choice, but I don't know what else to use) and can access each other's UI elements without causing death and destruction? Or do I need to explicitly jump back to the Main/Original STA thread and ONLY ever create UI elements from THAT (not just ANY) STA thread?

  2. If that is the case (only 1 STA thread is allowed to make UI elements) how do I do that correctly? I have seen many posts that related to this but for some reason I can't quite catch what's going on, and would love a REAL simple answer.

Please no 'Here's a cool slick way of doing...' I just need the simple way of at the point of execution where I need some UI elements jumping back over to the main STA thread if that's what's necessary.

If it is not necessary, then I will just make that worker thread an STA thread and continue on my way, is that fair? Or am I courting disaster?

2

There are 2 answers

8
i3arnon On BEST ANSWER
  1. If a thread creates a control. Only this specific thread can interact with it, even if there are other STA threads.
  2. In WinForms you would invoke a method on the control: Control.Invoke.In WPF you have the dispatcher to do it: Dispatcher.Invoke.

WinForms:

form1.Invoke(/* a delegate for your operation */)

WPF:

window1.Dispatcher.Invoke(/* a delegate for your operation */)

What you do is instead of changing an object in a "single apartment" you ask (invoke) the STA thread in control of it to do it (the delegate you invoke) for you. You also have BeginInvoke for doing it asynchronously.

0
Orion Edwards On

if the main thread is STA and creates a Window, adds some UI elements to it. Then spawns off another STA thread, and that second thread likewise creates some UI elements, are they doing it in the same 'space' [snip...] and can access each other's UI elements without causing death and destruction?

If Thread A and B are both STA, then they can each create and update their own UI elements, but not eachothers. Any other threads that want to affect the UI have to use one of the BeginInvoke style methods to ask the appropriate thread to do the update.

If it is not necessary, then I will just make that worker thread an STA thread and continue on my way, is that fair? Or am I courting disaster?

You may not be able to make the worker thread an STA thread if it's been set to MTA and initialized. You may have to make a new thread.

How do you do it? It seems like you want to use WPF (System.Windows.*) for your UI - so If the app that you are "plugging into" is also using WPF, you should be able to access it and re use it's UI thread. If not, you can make a new thread, and create a new Application on it and call Run. This should set up a dispatcher for you.

Something like this (pseudo code sort-of copied from some working code I have elsewhere)

Dispatcher dispatcher = null; // we 'get to' the UI thread via the dispatcher

if(Application.Current) { // re use an existing application's UI thread
    dispatcher = Application.Current.Dispatcher;
} else {
    var threadReadyEvent = new ManualResetEvent(false);

    var uiThread = new Thread(() => {
        Thread.CurrentThread.SetApartmentState(ApartmentState.STA);

        var application = new Application();
        application.Startup += (sender, args) => {
            dispatcher = application.Dispatcher;
            threadReadyEvent.Set();
        };

        // apps have to have a "main window" - but we don't want one, so make a stub
        var stubWindow = new Window {
            Width = 1, Height = 1, 
            ShowInTaskbar = false, AllowsTransparency = true,
            Background = Brushes.Transparent, WindowStyle = WindowStyle.None
        };
        application.Run(stubWindow);
    }){ IsBackground = true };

    uiThread.Start();
    threadReadyEvent.WaitOne();
    threadReadyEvent.Dispose();
}

dispatcher.Invoke(() => {
    // ask the UI thread to do something and block until it finishes
});

dispatcher.BeginInvoke(() => {
    // ask the UI thread to do something asynchronously
});

and so forth