【问题标题】:How to fix Task.Run from UI thread throwing STA error来自 UI 线程的 Task.Run 引发 STA 错误
【发布时间】:2017-04-24 14:59:44
【问题描述】:

当我使用 Office.Interop 库重构一些用于文档生成的旧 C# 代码时,我发现了这一点,因为它在调用函数时使用了 UI 上下文,所以它被阻塞了

例子

private void btnFooClick(object sender, EventArgs e)
{
      bool documentGenerated = chckBox.Checked ? updateDoc() : newDoc();

      if(documentGenerated){
        //do something
      }
}

决定这样改变它以减少阻塞 UI

private async void btnFooClick(object sender, EventArgs e)
{
      bool documentGenerated; = chckBox.Checked ? updateDoc() : newDoc();

      if(chckBox.Checked)
      {
                documentGenerated = await Task.Run(() => updateDoc()).ConfigureAwait(false);
      }
      else
      {
                documentGenerated = await Task.Run(() => newDoc()).ConfigureAwait(false);
      }

      if(documentGenerated){
        //do something
      }
}

它正在抛出这样的错误

当前线程必须设置为单线程单元 (STA) 模式 在进行 OLE 调用之前

为什么会发生这种情况以及可能的解决方法是什么?

【问题讨论】:

  • 你正在 updateDoc() 中做一些你不应该在工作线程中做的事情。我们看不到它。从您使用剪贴板或 OpenFileDialog 之类的 shell 对话框的异常消息来看,与 Office 互操作无关。不要那样做。请记住,互操作代码无论如何都在 UI 线程上运行,COM 会自动保持线程安全,因此您可能不会领先。
  • @HansPassant 好吧,是的,至少看到了MessageBox 来电
  • @HansPassant 哈哈,剪贴板也在那里使用。 3.5年开发经验,对这类问题一窍不通,有点害怕
  • 是的,害怕穿线的野兽,只有这样才能避免被活生生吃掉。它不是 MessageBox,它默默地让你做错了。盒子出现在另一个窗口后面的可能性总是不错的,用户永远不会看到它,一切都停止了。它是剪贴板。
  • @HansPassant wellp,有没有关于 STA、MTA 之类的好的实践文章?

标签: c# multithreading office-interop


【解决方案1】:

通过 Interop 访问的 COM 组件要求调用线程是 STA 线程,但在您的情况下它不是 STA。使用 STA 可以通过多个线程访问组件。您可以在Understanding and Using COM Threading Models 中阅读有关为什么需要 STA 的更多信息您可以按照此answer 中的建议在 Task 类上创建扩展方法,以使用任务通过 Interop 调用 COM 组件。

public static Task<T> StartSTATask<T>(Func<T> func)
{
    var tcs = new TaskCompletionSource<T>();
    Thread thread = new Thread(() =>
    {
        try
        {
            tcs.SetResult(func());
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        }
    });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return tcs.Task;
}

你也可以用线程代替任务,你必须像thread.SetApartmentState(ApartmentState.STA)一样将ApartmentState设置为STA

【讨论】:

  • 感谢您的回答。似乎它很复杂,然后我就很复杂了,谢谢你的链接
  • 是的。您不能只将石器时代的一些代码(Office 互操作)转换为使用 Tasks。它不是为它而构建的,所以你需要多一点才能让它工作。
  • 有人对你的答案投反对票._。不管你是谁,如果你读到这篇文章,你能解释一下,为什么和哪里错了吗?
【解决方案2】:

因为在这种情况下Task 可能会启动一个不是 STA 线程的新线程。您对updateDocnewDoc 的调用是调用互操作层的调用,它不喜欢MTA 线程。

您可以将其重构为使用Thread 而不是Task,并自行将公寓设置为STA。不过我会小心,因为我不确定 Interop 是否喜欢多线程。

【讨论】:

  • 感谢您的回答。我是异步和并行编程的新手(嗯,有点,当我还是学生的时候做了一些 CUDA 积分执行,但这只是一个 cs 数学任务)但是,它有什么意义呢?我的意思是逻辑上 ui threadinterop thread 没有绑定所以
  • UI 线程必须是 STA 线程,所以它总是在那个线程上工作。
猜你喜欢
  • 1970-01-01
  • 2018-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-02
  • 1970-01-01
相关资源
最近更新 更多