Click here to Skip to main content
15,913,349 members
Please Sign up or sign in to vote.
3.00/5 (1 vote)
See more:
I'm not a seasoned WPF guy, but in the past using a background thread has worked just fine for me, but in this instance I need to open a little window (CallBubble) from my MainWindow, and I'm getting all kinds of "The calling thread must be STA, because many UI components require this" grief. The window is not being created and opened on the main UI thread because it freezes the main window and the customer has been complaining (on XP it actually cripples the main window), hence the effort to get it on its own thread.

This is how I was doing it with a background worker (just the relevant parts shown):
C#
private void CaptureEvent(){

   ourBackgroundWorker.DoWork += OpenCallBubble;
   ourBackgroundWorker.RunWorkerCompleted += BubbleBackgroundWorkerCompleted;
   ourBackgroundWorker.RunWorkerAsync();

}

private void OpenCallBubble(object sender, DoWorkEventArgs e){
   
   CallBubble ourCallBubble = new CallBubble();
   
}

...and I get the
HTML
"The calling thread must be STA, because many UI components require this"
error on the creation of the CallBubble, and from its constructor.

If anyone can modify the above to work either as a background worker (which I doubt), or as a custom thread, or implementing STA against it...I would be much appreciated. Please reply though with ALL the code required.

Thanks,

Barry
Posted
Updated 27-Mar-16 19:43pm
v3
Comments
Pheonyx 23-May-13 18:12pm    
Why do you want to run a UI on a different thread?
My understanding of threading is that you should only have one UI thread.
Sergey Alexandrovich Kryukov 23-May-13 18:14pm    
No. The problem is absolutely clear. Please see my solution.
—SA
Francisco T. Chavez 26-May-13 0:44am    
I have to ask, what is this new Window doing that's causing the main window not to work?

That's true. In particular, WPF application UI thread itself requires STA. This is easy to fix. For a main thread, this is controlled by the attribute applied to the application entry point:
C#
[STAThread]
static void Main() {
    //...
}

This is actually the only valid way to set a thread Apartment State for the start-up thread of the process, by the reason I explain below.

You may think that such thing does not exist in a WPF application, but that's not true. This code is simply generated for you by the application template (you can find it under your project when it is build, just do some text search), but in some cases you have to write this part of code manually, to achieve some customized behavior.

Now, the remaining problem is to set up the Apartment State for the thread you create during run time, which you need to do only if it is required by some code used by the thread. This is also not a problem. One thing to understand is: a thread cannot change its Apartment State by the code running in this thread. You can only do it in some other thread, usually the code where the thread is created and started, and only before starting it. Here:
http://msdn.microsoft.com/en-us/library/system.threading.thread.setapartmentstate.aspx[^].

(This is really needed in some question. One such case I remember is speech recognition. On of the speech recognizing classes requires only STA, another one — only MTA.)

That's it.

—SA
 
Share this answer
 
v3
Comments
Pheonyx 23-May-13 18:23pm    
I know this isn't my question.. but are you saying that you could have an application that could have different UI's running on different threads? Because I thought that even multi-threaded applications could only have a single UI thread?
Sergey Alexandrovich Kryukov 23-May-13 18:30pm    
This is not the topic of this conversation, but the answer is quite sure: yes, this too. I even managed to run two difference UIs in one process which belongs to two different platforms: native Mac OS X with Cocoa UI + System.Windows.Forms for Mono installed on Mac. Yes, two different threads in one process. Could you bit that?

Actually, recently I answered a question of one CodeProject inquirer who ran both WPF and Forms UIs in one application in two different thread; and the question was merely about some minimal collaboration between them (one UI should close another one). So, we are not along.

This question is just about Apartments, but one consequence is: if you have two different UI library, you can run the UI in one process, two different threads with two different Apartment States. With available FCL UI library it is not really required, because Forms actually can run under MTA as well as STA. But there are also 3rd-party library for .NET, such as GTK...

—SA
WPF is based heavily on DependencyObjects, which can only be hosted in a STA thread. So creating them in a non-STA thread results in exceptions. On top of that, those BackgroundWorders are there for doing background work, if you have a long running process that won't let go of the thread, use a BackgroundWorker. They're not intended to play host to WPF controls.

So, if you want to create a new Window outside of the UI thread, you need to create a new Thread (not thread) and call the SetApartmentState method with a ApartmentState.STA value for the parameter.

C#
Thread otherWindowHostingThread = new Thread(new ThreadStart(OtherThreadStartPoint));
otherWindowHostingThread.SetApartmentState(ApartmentState.STA);
otherWindowHostingThread.Start();


The method you use as your thread starting point needs to call the new Window's Show() method, and it should also tell the that thread's Dispatcher to start before the method finishes executing.

I'm not 100% sure how well this will work out, because I've only done this when I was first leaning WPF on .net 3.5, and the only reason I did it was because I was learning WPF. I should also warn you that, any Windows created outside of the App's UI thread will not be inserted in the App's Windows collection property.

A full solution to this can be found in chapter 8 section 16 of "WPF Recipes in C# 2008, A Problem-Solution Approach" by Sam Noble, Sam Bourton, and Allen Jones.
 
Share this answer
 
C#
// enable unit test to mock a dispatcher
var dispatcher = Dispatcher.CurrentDispatcher;
if (Application.Current != null)
{
    // use the application dispatcher if running from the software
    dispatcher = Application.Current.Dispatcher;
}

if (dispatcher != null)
{
    // delegate the operation to UI thread.
    dispatcher.Invoke(
        delegate
        {
            MessageBox.Show("Opeartion could not be completed. Please try again.","Error",MessageBoxButton.OK,MessageBoxImage.Error);
        });
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900