Search code examples
c#multithreadingtaskoffice-interop

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


While I was refactoring some old C# code for document generation with the Office.Interop library, I found this and because of it was using UI context. When functions were called from it it was blocking it

For example:

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

I decided to change it to reduce from blocking 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
      }
}

It was throwing this error:

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

Why does it happen and what is the workaround?


Solution

  • The COM components accessed through Interop require the calling thread to be a STA thread but in your case it is not STA. Otherwise the STA component could be accessed through multiple threads. You can read more about why STA is required in Understanding and Using COM Threading Models.

    You can make a extension method on Task class as suggested in Set ApartmentState on a Task to call the COM component through Interop using task:

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

    When you use Thread instead of task, you have to set the ApartmentState to STA using something like thread.SetApartmentState(ApartmentState.STA).