我正在使用ExcelDna和异步功能。如果async:d代码中有异常,我想显示一个花哨的WPF错误窗口。我的问题是我得到错误"调用线程必须是STA,因为许多UI组件都需要这个。"我怎么解决这个问题?
[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);
}
}
答案 0 :(得分:3)
当您的Excel函数运行时,没有安装SynchronizationContext.Current
,因此async / await机制将在ThreadPool线程上运行await
之后的代码(包括您的catch处理程序)。这不是您可以直接显示WPF表单的上下文。
安装与主线程(或另一个线程)上运行的Dispatcher相对应的DispatcherSynchronizationContext会起作用,但是您必须为每个UDF调用执行此操作。不知何故,通过Excel的本机代码路径丢失了主线程上的.NET调用上下文,因此SynchronizationContext丢失了。
最好可能假设catch处理程序在ThreadPool线程上运行,并从catch处理程序进行SynchronizationContext.Post
调用,将您带回运行Dispatcher和WPF表单的主线程。
您可以查看Excel-DNA如何实现(WinForms)LogDisplay窗口。 (https://github.com/Excel-DNA/ExcelDna/blob/master/Source/ExcelDna.Integration/LogDisplay.cs)。您可以从任何主题调用LogDisplay.WriteLine(...)
,它会_syncContext.Post
运行&#39;显示&#39}。在主线上。
C#async / await机制对Excel的效果不太好,因为本机/托管转换以及Excel内部的任何操作都会破坏需要在continuation之间流动的线程上下文。即使在.NET方面,也不清楚如何在AppDomains(不同的Excel加载项)之间管理线程上下文。因此,最好不要依赖.NET运行时能够通过托管/本机转换来处理任何类型的上下文。
答案 1 :(得分:2)
许多Office插件都存在SynchronizationContext.Current
为null
的问题,异步延续在线程池上执行。我会在第一个SynchronizationContext.Current
之前检查await
的值。
我在创建WinFormsSynchronizationContext
并在第一个await
之前在线程上安装它时取得了一些成功。但是,安装WPF上下文会更复杂。