Niels Bosma
Niels Bosma

Reputation: 11498

ExcelDna: Async: The calling thread must be STA

I'm working with ExcelDna and async functions. If there's an exception in the async:d code I want to show a fancy WPF error window. My problem is that I'm getting the error "The calling thread must be STA, because many UI components require this." How can I solve this?

    [ExcelFunction(Description = "", Category = "")]
    public static async Task<object> /*string*/ Foo(CancellationToken ct)
    {
        try
        {
            return await Task.Run(async () =>
            {
                await Task.Delay(1000, ct);
                throw new Exception("BOO");
                return "HelloWorld";
            }, ct2.Token);
        }
        catch (Exception e)
        {
            return ShowWpfErrorWindowThatRequiresSTA(e);
        }
    }

Upvotes: 1

Views: 2658

Answers (2)

Govert
Govert

Reputation: 16907

When your Excel function runs there is no SynchronizationContext.Current installed, so the async/await mechanism will runs the code after await (including your catch handler) on a ThreadPool thread. That's not a context where you can directly show your WPF form.

Installing a DispatcherSynchronizationContext corresponding to a Dispatcher running on the main thread (or another thread) would work, but you have to do that for every UDF call. Somehow the native code path through Excel loses the .NET call context on the main thread, so the SynchronizationContext gets lost.

Better is probably to assume that the catch handler is running on a ThreadPool thread, and make a SynchronizationContext.Post call from the catch handler to take you back to the main thread running your Dispatcher and WPF form.

You can look at how Excel-DNA implements the (WinForms) LogDisplay window. (https://github.com/Excel-DNA/ExcelDna/blob/master/Source/ExcelDna.Integration/LogDisplay.cs). You can call LogDisplay.WriteLine(...) from any thread, and it will do a _syncContext.Post to run the 'Show' on the main thread.

The C# async/await mechanism works less well with Excel since the native/managed transitions, and whatever Excel does internally, messes up the thread context that needs to flow between continuations. Even on the .NET side, it's not clear how thread context is managed between AppDomains (different Excel add-ins). So it's best not to rely on the .NET runtime being able to thread any kind of context through the managed/native transitions.

Upvotes: 3

Stephen Cleary
Stephen Cleary

Reputation: 456537

Many Office plugins have a problem where SynchronizationContext.Current is null, and asynchronous continuations execute on the thread pool. I'd check the value of SynchronizationContext.Current before the first await.

I have had some success in creating a WinFormsSynchronizationContext and installing that on the thread before the first await. However, installing a WPF context would be more complex.

Upvotes: 2

Related Questions