3

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?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
DanilGholtsman
  • 2,354
  • 4
  • 38
  • 69
  • 1
    You are doing something in updateDoc() that you should never do from a worker thread. We can't see it. Nothing to do with Office interop, judging from the exception message you are using the clipboard or a shell dialog like OpenFileDialog. Don't do that. And keep in mind that the interop code runs on the UI thread anyway, COM keeps it thread-safe automatically so you are probably not ahead. – Hans Passant Dec 09 '16 at 07:58
  • @HansPassant well, yeah, saw there `MessageBox` calls at least – DanilGholtsman Dec 09 '16 at 08:16
  • @HansPassant haha, and clipboard uses there too as well. Got 3.5 years of developing experience, got no idea about such problems and scared a little – DanilGholtsman Dec 09 '16 at 08:19
  • 1
    Yes, fear the threading beast, only way to avoid being eaten alive. It is not MessageBox, it silently lets you do it wrong. Always decent odds that the box shows up behind another window, the user will never see it and everything grinds to a halt. It is Clipboard. – Hans Passant Dec 09 '16 at 08:52
  • @HansPassant wellp, is there any good practice articles about STA, MTA and stuff? – DanilGholtsman Dec 12 '16 at 10:38
  • What's the intention behind the use of `.ConfigureAwait(false)`? – Theodor Zoulias Jan 20 '22 at 19:24

2 Answers2

5

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).

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Adil
  • 146,340
  • 25
  • 209
  • 204
1

Because in this case Task presumably starts a new thread that isn't an STA thread. Your calls to updateDoc and newDoc are the ones that call the Interop layer, which doesn't like MTA threads.

You could refactor this to use Thread instead of Task and set the apartment to STA by yourself. I would be careful though, because I am not sure Interop likes multi-threading.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Thanks for answer. I'm new to async and parallel programming (well, sort of, when I was student made some CUDA integral executions but it was just a cs math task) but, well, how does it makes sense? I mean logically `ui thread` and `interop thread` are not binded so – DanilGholtsman Dec 09 '16 at 07:48
  • The UI thread must be an STA thread, so it always works on that thread. – Patrick Hofman Dec 09 '16 at 08:15