如何将后台工作线程设置为单线程公寓?

28

我正在创建一个自动化测试运行应用程序。在该应用程序的这一部分中,我正在开发一个轮询服务器。它通过不断轮询web服务器来确定何时应运行新的自动化测试(用于GUI应用程序的夜间自动运行)。

当轮询服务器看到请求时,它会下载所有必要的信息,然后在后台工作线程中执行测试运行。问题是测试运行的某些部分包含OLE、COM和其他调用(例如Clipboard.Clear()),这些调用发生在后台工作线程中。当其中一个这样的调用发生时,将引发以下异常:

必须将当前线程设置为单线程公寓 (STA) 模式才能进行 OLE 调用。请确保您的 Main 函数上已标记 STAThreadAttribute。

如何将后台工作线程标记为单线程公寓?我的Program.cs文件中的Main函数已经有了该属性。


Clipboard.Clear() 不是 COM,而是本地的 Windows API。 - Aliostad
7
剪贴板使用COM来协商剪贴板数据和格式。 - Hans Passant
5个回答

37

这是不可能的,BGW使用线程池线程。线程池线程始终为MTA,不能更改。您必须使用常规线程,在启动它之前调用SetApartmentState()方法。此线程还应该泵送一个消息循环,调用Application.Run()方法。

也许你应该考虑从UI线程调用这段代码。因为很可能,COM服务器已经在UI线程上运行其方法了。将工作线程中的调用代理到创建COM服务器的STA线程是自动完成的,COM会处理这个过程。

或者你可以自己实现代理过程。你可以创建自己的STA线程来为服务器提供良好的执行环境。你可以在这篇帖子中找到相关代码,确保在Initialize()重写方法中创建COM对象。


我想这是有道理的,因为我真的看不出在测试运行时与应用程序进行交互有什么用处。 - KallDrexx

8

BackgroundWorker默认使用线程池线程,但您可以覆盖此行为。首先,您需要定义一个自定义的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);
    }
}

在使用BackgroundWorker之前,可以像以下这样覆盖默认的同步上下文:

   AsyncOperationManager.SynchronizationContext = new MySynchronizationContext();

注意:这可能会对您的应用程序的其他部分产生性能影响,因此您可能希望限制新的Post实现(例如使用state或d参数)。


对我没有用。DoWork方法仍然从MTA线程调用。 - Maxence
谢谢!这对我有用。> Thread t = new Thread(new ThreadStart(() => { Clipboard.Clear(); }));t.SetApartmentState(ApartmentState.STA);t.Start(); - jaysonragasa

7

虽然我没有测试过,但如果调用WinForms表单,您应该会回到UI线程,并且大部分功能应该再次正常工作。

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
    });
}

这对我有用!谢谢!但是“调用winform”实际上是什么意思?在使用它时是否存在任何性能相关问题? - Shameel Mohamed

1
我使用了Conrad de Wet的想法,效果很好!
不过这段代码有一个小问题,你必须像这样关闭“this.Invoke.....”:});
这是Conrad de Wet的代码,并进行了修复:
    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
        });
    }

8
不留下评论就点踩实在是太愚蠢了,我想他的意思是告诉你,你的答案不好,因为它只纠正了一些语法错误。相反,你应该对Conrad de Wet的回答进行评论或编辑。 - n.Stenvang

1
通常情况下,你可以通过在入口点(例如静态Main方法)上定义[STAThread()]属性来设置它。

1
这仅适用于Main方法,而不适用于后台任务。 - Kevin Smyth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接