【问题标题】:Deadlock while using async/await使用 async/await 时出现死锁
【发布时间】:2015-03-24 11:20:59
【问题描述】:

我正在努力理解awaitasync

效果很好。但现在我陷入了僵局。

我用false 调用了ConfigureAwait,就像在this article 中一样,但我的代码仍然阻塞。

这是我的代码的一个小sn-p:

private void button1_Click(object sender, EventArgs e)
{
    var result = HeavyWorkAsync().Result;
    richTextBox1.AppendText(result);
}

private string HeavyWork()
{
    for (var index = 0; index < 1000; index++)
    {
        Task.Delay(10).Wait();
    }

    return "finished";
}

private async Task<string> HeavyWorkAsync()
{
    var task = await Task.Factory.StartNew<string>(HeavyWork).ConfigureAwait(false);
    return task;
}

【问题讨论】:

  • 你能解释一下为什么你不想做await HeavyWorkAsync()吗?
  • 在我看来,这很丑。按钮的点击处理程序中的异步?
  • 这是处理这种情况的推荐方法,最重要的是它不会阻塞或死锁:)
  • 在上面?如果这不是死锁,就像文章中那样,这还能做什么?
  • async void 处理这种情况是合法的。是的,async void 很丑陋,应该在每个不是订阅事件的签名中避免使用,但我们别无选择,因为我们使用的 UI 框架不支持正确的异步回调。

标签: c# async-await deadlock


【解决方案1】:

阻塞的不是任务本身,而是对Result 的调用。一个Task代表一个异步操作,但是调用它的Result属性,或者调用Wait()会阻塞当前线程,直到方法返回。而且在很多情况下,它会导致死锁,因为任务无法完成,它的调用线程被阻塞!

为防止这种情况,请使用 asyncawait 异步链接任务

private async void button1_Click(object sender, EventArgs e)
{
    var result = await HeavyWorkAsync(); // <=== await
    richTextBox1.AppendText(result);
}

此外,Task.Delay(10).Wait(); 首先完全违背了使用任务的目的:这将阻塞当前线程。如果那真的是您想要做的(而且不太可能),请致电Thread.Sleep(10);,它会让您的意图更加清晰,并且您可以跳过的障碍更少。或者更好的是,在异步方法中使用 await Task.Delay(10);

关于ConfigureAwait

ConfigureAwait(false) 到底是做什么的?

它消除了任务继续在与任务调用者相同的上下文中运行的义务。在大多数情况下,这意味着不再保证继续在相同的上下文中运行。因此,如果我有一个方法,可以使用 Foo(),稍等一下然后 Bar() 就像这样:

async Task DoStufAsync()
{
    Foo();
    await Task.Delay(10);
    Bar(); // run in the same context as Foo()
}

我保证 Bar 将在相同的上下文中运行。如果我有ConfigureAwait(false),就不是这样了

async Task DoStufAsync()
{
    Foo();
    await Task.Delay(10).ConfigureAwait(false);
    Bar(); // can run on another thread as Foo()
}

当你使用ConfigureAwait(false) 时,你告诉你的程序你不介意上下文。它可以解决一些死锁问题,但通常不是正确的解决方案。正确的解决方案很可能永远不会以阻塞方式等待任务,并且一直是异步的。

【讨论】:

  • 请注意,文章 / OP 正在尝试使用 ConfigureAwait 以避免首先必须使点击处理程序 async
  • 问题是ConfigureAwait 没有这样做:UI 线程将因为调用Result 而变得无响应,无论方法的内部工作如何。他所能期望的最好的结果是有一个巨大的丑陋的冻结,最终会结束,而不是无限的僵局。
  • 您能否在回答中解释一下以完成它,因为这是问题的主要焦点-我自己对异步内容还很陌生,但似乎这篇文章暗示可以这样使用
  • @JamesThorpe:我当然可以,而且现在就可以!
  • await 不在同一个线程上恢复;它在相同的 context 上恢复。 UI 上下文与线程具有一对一的关系,但其他上下文(例如 ASP.NET、线程池等)则没有。
【解决方案2】:

要扩展 Falanwe 的答案,您应该查看 Stephen Cleary's blog post。根据代码,我假设您使用的是 Windows 窗体应用程序,因此对 Task.Result 的调用将在 UI 上下文中执行任务,从而阻塞 UI 线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2012-09-23
    • 2013-08-09
    相关资源
    最近更新 更多