如何从 UI 线程抛出 STA 错误修复 Task.Run

How to fix Task.Run from UI thread throwing STA error

当我重构一些旧的 C# 代码以使用 Office.Interop 库生成文档时,我发现了这个,因为它使用了 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
      }
}

它抛出了这个错误:

Current thread must be set to single thread apartment (STA) mode
before OLE calls can be made

为什么会发生这种情况,解决方法是什么?

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

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

通过 Interop 访问的 COM 组件要求调用线程是 STA 线程,但在您的情况下它不是 STA。否则,可以通过多个线程访问 STA 组件。您可以在 Understanding and Using COM Threading Models.

中详细了解为什么需要 STA

您可以按照 Set ApartmentState on a Task 中的建议在任务 class 上创建扩展方法,以使用任务通过 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 而不是 task 时,您必须使用类似 thread.SetApartmentState(ApartmentState.STA).

的方式将 ApartmentState 设置为 STA