3

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?

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
Aaron Marcus
  • 153
  • 2
  • 13
  • 1
    STA and MTA are terms that are relevant to COM programming. Are you actually doing COM? If not then just focus on the need to keep UI thread-safe and use the basic primitives provided by the class library you use to marshal the call. Control.Begin/Invoke() in Winforms, Dispatcher.Begin/Invoke() in WPF or Store apps. – Hans Passant Jan 06 '14 at 23:21
  • This is the error I am getting: System.InvalidOperationException: The calling thread must be STA, because many UI components require this. I assume its an STA/MTA problem because of that. I am not explicitly making any COM use (that I know of) but I am writing a plugin for a (much) larger piece of software that I do know is making use of COM. – Aaron Marcus Jan 06 '14 at 23:25
  • You should know more about the architecture of that "larger" piece of software for which you're writing a plugin. Some apps have multi-thread UI, where each main UI window lives in its own thread. To name a few: MS Word, Windows Explorer. Can that be the case with your host app? – noseratio Jan 06 '14 at 23:37

2 Answers2

3

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

Orion Edwards
  • 121,657
  • 64
  • 239
  • 328
2
  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.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • I guess I am doing WinForms (new to C#) so the idea would be what, at the outset create a Dispatcher object singleton-style for the thread I want to create all UI elements off of and just grab him whenever I need him? Sorry, I think I am doing WPF looking at your edit. I am just creating a Window programmatically and then adding Controls to panels and making the panel the content of the Window to that panel. WPF, correct? – Aaron Marcus Jan 06 '14 at 23:22
  • dispatcher is wpf. in winforms you have forms which are basically windows. they have buttons and text boxes which are controls. if you have some non-STA thread that wants to update the UI. It needs a reference to that form (let's say _mainForm is the name) and just call invoke `_mainForm.Invoke(() => _button.Text="New Button");` – i3arnon Jan 06 '14 at 23:26
  • I don't have any forms running around. The main piece of software is something that's a black box to me. On my side I am creating a new window (System.Windows.Window win = new Window()) and adding controls to it. Not trying to be too tricky here :) So I need to create that window ahead of time on the main thread before I get over to the secondary thread? – Aaron Marcus Jan 06 '14 at 23:32
  • @AaronMarcus System.Windows is the WPF namespace so you're not using winforms. in this case you need to use the dispatcher: `win.Dispatcher.Invoke(() => /* whatever code you need to run under the STA thread */)` – i3arnon Jan 06 '14 at 23:36
  • Ok. But I would still need to create the window on the main thread before I got to the secondary thread in order to be able to call invoke, correct? – Aaron Marcus Jan 06 '14 at 23:39
  • @AaronMarcus you have in the System.Windows namespace `Application.Current.Dispatcher` which gives you the dispatcher for the whole WPF application which you can `Invoke` on. – i3arnon Jan 06 '14 at 23:48
  • I would pop this to chat, but I don't have enough 'street cred' yet for that option, apologies for the continued back and forth :) From that Dispatcher I would be accessing from the Main thread as though I had never spawned off any other threads, correct? so on a drawing function (called by the overarching app I am plugging in to) I would be able to access anything I do via `Application.Current.Dispatcher` correct? – Aaron Marcus Jan 06 '14 at 23:53
  • Yes. Whatever is inside the Invoke will be scheduled on the UI thread. but I would keep that interaction to a minimum because it isn't trivial and using it incorrectly can cause problems. – i3arnon Jan 06 '14 at 23:55