ExcelDna: Async: вызывающий поток должен быть STA

Я работаю с ExcelDna и асинхронными функциями. Если в коде async: d есть исключение, я хочу показать необычное окно с ошибкой WPF. Моя проблема в том, что я получаю сообщение об ошибке «Вызывающий поток должен быть STA, потому что этого требуют многие компоненты пользовательского интерфейса». Как я могу это решить?

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

person Niels Bosma    schedule 07.04.2016    source источник
comment
Вы должны отобразить окно в потоке пользовательского интерфейса. Используйте Application.Current.Dispatcher.BeginInvoke ().   -  person Hans Passant    schedule 08.04.2016


Ответы (2)


Когда ваша функция Excel работает, SynchronizationContext.Current не установлен, поэтому механизм async / await будет запускать код после await (включая ваш обработчик catch) в потоке ThreadPool. Это не тот контекст, в котором вы можете напрямую показать свою форму WPF.

Установка DispatcherSynchronizationContext, соответствующего Dispatcher, работающему в основном потоке (или другом потоке), будет работать, но вы должны делать это для каждого вызова UDF. Каким-то образом путь к машинному коду через Excel теряет контекст вызова .NET в основном потоке, поэтому SynchronizationContext теряется.

Лучше, вероятно, предположить, что обработчик catch работает в потоке ThreadPool, и сделать SynchronizationContext.Post вызов из обработчика catch, чтобы вернуть вас обратно в основной поток, в котором запущены ваши Dispatcher и форма WPF.

Вы можете посмотреть, как Excel-DNA реализует окно LogDisplay (WinForms). (https://github.com/Excel-DNA/ExcelDna/blob/master/Source/ExcelDna.Integration/LogDisplay.cs). Вы можете вызвать LogDisplay.WriteLine(...) из любого потока, и он выполнит _syncContext.Post, чтобы запустить «Показать» в основном потоке.

Механизм async / await C # работает хуже с Excel, поскольку собственные / управляемые переходы и все, что Excel делает внутри, искажает контекст потока, который должен течь между продолжениями. Даже на стороне .NET неясно, как осуществляется управление контекстом потока между доменами приложений (разными надстройками Excel). Поэтому лучше не полагаться на то, что среда выполнения .NET может передавать любой контекст через управляемые / собственные переходы.

person Govert    schedule 08.04.2016
comment
Работает! Я создаю статический контекст WindowsFormsSynchronizationContext (кажется, работает и для WPF?) В моем AutoOpen, который я использую для внутренних целей в HandleError. - person Niels Bosma; 10.04.2016
comment
Как ты имеешь в виду, что он не останется? Как бы ты это сделал? - person Niels Bosma; 10.04.2016

Многие подключаемые модули Office имеют проблему, когда SynchronizationContext.Current равно null, а асинхронные продолжения выполняются в пуле потоков. Я бы проверил значение SynchronizationContext.Current перед первым await.

Мне удалось создать WinFormsSynchronizationContext и установить его в потоке перед первым await. Однако установка контекста WPF была бы более сложной.

person Stephen Cleary    schedule 07.04.2016