【问题标题】:How to block a method while an asyncronous method is running without causing deadlocks in the UI thread如何在异步方法运行时阻止方法而不导致 UI 线程死锁
【发布时间】:2017-03-22 15:43:53
【问题描述】:

我有几个异步方法需要同步回主 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 无关)都必须从 main/ui 线程执行。

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

【问题讨论】:

  • 你不需要阻塞你的 UI 线程。只要您需要阻止您的 UI 线程(特别是当其他进程实际上需要使用 UI 线程时),那么您将处于一个受伤的世界。如果您重视自己的理智,请删除该要求。
  • 为什么使用同步事件处理程序?为什么不是 *asynchronous& 事件处理程序?例如async void Button_Click(..){ await MyAsyncMethod1(); await MyAsyncMethod2();}
  • 另一种选择是使用 Progress 将消息发送回 UI 线程。 Progress 的有效负载不必是字符串或进度消息,它可以是任何对象。检查Async in 4.5: Enabling Progress and Cancellation in Async APIs
  • 问题是事件处理程序不是“我的”。这是 Autodesk Inventor 的插件。我不能使用 async void 因为事件处理程序有参数:void Eventhandler(..., out EventhandlingEnum handled) 我需要确保在我的方法完成之前发明者主机应用程序不会继续

标签: c# async-await


【解决方案1】:

我发现的唯一解决方案对我来说似乎是一个 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。但是,我强烈建议您重构代码而不是采用可重入性。

【讨论】:

  • 感谢您的快速回复。重构是我的首选解决方案,但这是一项艰巨的任务,因此必须逐步完成。如何确定我的代码在哪里等待 UI 线程消息队列被抽取?
  • @Henrik:你可以使用AsyncContext,然后在死锁时查看你的 UI 线程上的调用堆栈。
猜你喜欢
  • 2021-07-13
  • 2016-01-31
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
  • 2020-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多