Skip to main content
 首页 » 编程设计

c#之如何在异步方法运行时阻止方法而不导致 UI 线程中的死锁

2024年01月21日28yxwkf

我有几个异步方法需要同步回主 ui 线程。

async Task MyAsyncMethod() { 
    await DoSomeThingAsync().ConfigureAwait(true); 
    DoSomethingInTheGui(); 
} 

现在我需要从一个由 gui 线程触发的同步事件处理程序调用它们,并且在异步方法完成之前事件处理程序无法完成。所以 MyAsyncMethod().Wait() 不是一个选项,也不是某种一劳永逸的解决方案。

这种使用 Nito.AsyncEx 的方法看起来很有前途,但它仍然死锁:https://stackoverflow.com/a/9343733/249456

我找到的唯一解决方案对我来说似乎是一个 hack:

public void RunInGui(Func<Task> action) 
{ 
    var window = new Window(); 
    window.Loaded += (sender, args) => action() 
       .ContinueWith(p => { 
            window.Dispatcher.Invoke(() => 
            { 
                window.Close(); 
            }); 
        }); 
        window.ShowDialog();  
} 

有没有办法在不创建新窗口的情况下获得相同的效果(阻止调用方法,允许同步回 gui 线程)?

注意:我知道重构可能是最好的选择,但这是一项艰巨的任务,我们必须在更长的时间内完成。另外值得一提的是,这是 Autodesk Inventor 的插件。该 api 有几个怪癖,所有 API 调用(甚至与 ui 无关的调用)都必须从主/ui 线程执行。

另外值得一提的是,我们保留了对主线程调度程序的引用,并在整个代码库中使用了 MainThreadDispatcher.InvokeAsync(() => ... )。

请您参考如下方法:

The only solution i've found seems like a hack to me

所有同步异步解决方案都是 hack。我在 article on brownfield async 中列举了最常见的那些.请务必注意,不存在针对任意代码的解决方案。 每个解决方案在某些情况下要么死锁要么抛出异常。

一种更简单的技巧是阻塞线程池线程:

Task.Run(() => action()).Wait(); 

但是,如果您的 action() 需要 UI 上下文,那么这将不起作用。

下一步是尝试使用单线程上下文。这是我的 AsyncContext 类型采用的方法:

AsyncContext.Run(() => action()); 

如果 action 正在等待 UI 线程的消息队列被抽取,这将死锁。由于此死锁但 Window 有效,因此情况似乎如此。

进一步下降一个级别,您可以使用 nested pumping .请注意 nested pumping opens up a whole world of hurt due to unexpected reentrancy . GitHub 上有一些代码显示 how to do nested pumping on WPF .但是,我强烈建议您重构代码而不是拥抱可重入。