28

I am creating an automated test running application. In this part of the application, I am working on a polling server. It works by constantly polling the web server to determine when a new automated test should be run (for nightly automated runs of our GUI application).

When the polling server sees a request, it downloads all the information necessary and then executes the test run in a background worker. The problem is that part of the test run has OLE, COM, and other calls (for example, Clipboard.Clear()) that occur in the background worker thread. When one of these calls occurs, the following exception occurs:

Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.

How can I mark a background worker thread as single thread apartment? The Main call in my Program.cs obviously already has that attribute.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
KallDrexx
  • 27,229
  • 33
  • 143
  • 254

5 Answers5

37

This is not possible, BGW uses a threadpool thread. TP threads are always MTA, it cannot be changed. You will have to use a regular Thread, call SetApartmentState() before you start it. This thread also should pump a message loop, call Application.Run().

Maybe you ought to consider calling this code from the UI thread. Because in all likelihood, the COM server is running its methods on the UI thread anyway. Marshaling calls from a worker thread to the STA thread that created the COM server is automatic, COM takes care of it.

Or take the bull by the horns and marshal yourself. You can create your own STA thread to give the server a happy home. You'll find code in this post, be sure to create the COM object in your Initialize() override.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I guess that makes sense, since I can't really see a use for interacting with the application while a test is running – KallDrexx Jan 13 '11 at 21:22
8

BackgroundWorker uses by default a ThreadPool thread, but you can override this behavior. First you need to define a custom SynchronizationContext:

public class MySynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        Thread t = new Thread(d.Invoke);
        t.SetApartmentState(ApartmentState.STA);
        t.Start(state);
    }
}

And override the default SynchronizationContext, like this, before you use your BackgroundWorker:

   AsyncOperationManager.SynchronizationContext = new MySynchronizationContext();

NOTE: this can have performance effects on the rest of your application, so you might want to restrict the new Post implementation (for example using the state or d parameters).

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Not working for me. The DoWork method is still called from a MTA thread. – Maxence Jul 10 '15 at 14:41
  • thanks! this worked for me. > Thread t = new Thread(new ThreadStart(() => { Clipboard.Clear(); }));t.SetApartmentState(ApartmentState.STA);t.Start(); – jaysonragasa Jul 11 '15 at 15:35
7

I have not tested it, but if you invoke the WinForms Form, you should be back to the UI thread and most of the stuff should work again.

BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(this.bgw_DoWork);
bgw.RunWorkerAsync();

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    // Invoke the UI thread
    // "this" is referring to the Form1, or what ever your form is
    this.Invoke((MethodInvoker)delegate
    {
        Clipboard.GetText();
        // etc etc
    });
}
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
Conrad de Wet
  • 477
  • 6
  • 15
  • This works for me! Thanks! But what does "invoking the winform" actually mean? Is there any performance related issue in using this? – Shameel Mohamed Jul 18 '17 at 05:52
1

I used +Conrad de Wet's idea and it worked great!

There is one small issue with that code though, you have to close the "this.Invoke....." like with a });

Here is Conrad de Wet's code with this fix:

    BackgroundWorker bgw = new BackgroundWorker();
    bgw.DoWork += new DoWorkEventHandler(this.bgw_DoWork);
    bgw.RunWorkerAsync();>

    private void bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        // Invoke the UI thread
        // "this" is referring to the Form1, or what ever your form is
        this.Invoke((MethodInvoker)delegate
        {
            Clipboard.GetText();
            // etc etc
        });
    }
moont10
  • 31
  • 1
  • 8
    This downvote without leaving a comment on it is just stupid. So I guess what he meant was to tell you that your answer is bad cause it only corrects some syntax errors. What you should have done instead was to either comment on Conrad de Wet's answer or edit his answer. – n.Stenvang Oct 01 '14 at 11:41
1

You normally set it by defining attributre [STAThread()] on the entry point (e.g. Static Main).

Aliostad
  • 80,612
  • 21
  • 160
  • 208