【问题标题】:Difference between calling .Wait() on an async method, and Task.Run().Wait()在异步方法上调用 .Wait() 和 Task.Run().Wait() 之间的区别
【发布时间】:2016-11-28 08:20:18
【问题描述】:

我在 ASP.NET WebForms 网站上有一个服务器端点击事件。在这种情况下,我调用一个方法,该方法又调用其异步伙伴方法,在调用中添加.Wait()

然后,此方法会下降几个级别(,调用另一个异步方法,该方法调用另一个异步方法,依此类推)并最终在 HttpClient 对象上调用一个异步方法。在这一点上,线程似乎消失在一个兔子洞里;该方法从不回调。

现在,我知道异步系列调用按预期工作,因为同样的代码也从 Web API 控制器调用(控制器方法调用第一个方法的异步版本,而不是同步“伙伴”方法),这是完全异步的,它按预期返回。

所以基本上我有这样的东西,它永远不会返回

protected void btn_Click(object sender, EventArgs e)
    > Class1.DoSomething()
        > Class1.DoSomethingAsync.Wait()
            ...
                > await ClassN.Authenticate()
                  {
                    await myHttpClient.PostAsync()  // never returns
                  }

我确实尝试在第一个异步方法上使用.ConfigureAwait(false),但没有任何成功。

我也有这个,确实返回

Task<IHttpActionResult> MyWebApiMethod()
    > await Class1.DoSomethingAsync()
        ...
            > await ClassN.Authenticate()
              {
                await myHttpClient.PostAsync()  // does return
              }

我发现如果我将第一个版本更改为以下内容,我可以使它工作:

protected void btn_Click(object sender, EventArgs e)
    > Class1.DoSomething()
        > Task.Run(async () => await Class1.DoSomethingAsync()).Wait()
            ...
                > await ClassN.Authenticate()
                  {
                    await myHttpClient.PostAsync()
                  }

但我不知道为什么。

谁能解释一下调用的区别

Class1.DoSomethingAsync.Wait()

打电话

Task.Run(async () => await Class1.DoSomethingAsync()).Wait()

【问题讨论】:

  • 何不以正确的方式使用它。 async void btn_Clickawait Class1.DoSomethingAsync() ?
  • 不要将Task.Run 与已经异步的方法一起使用,这是对线程的浪费。只需按照@user3185569 的建议更改button_click eventhandler 的签名
  • @user3185569 - 我没有使用它,因为我不知道我可以在服务器端 WebForms 事件中使用 async。感谢您向我指出这一点。

标签: c# asp.net asynchronous async-await


【解决方案1】:

我在我的博文 Don't Block on Asynchronous Code 和我的 MSDN 文章 asynchronous best practices 中解释了这种行为。

线似乎从兔子洞里消失了;该方法从不回调。

发生这种情况是因为awaits 之一正试图在 ASP.NET 上下文中恢复,但请求线程(使用该 ASP.NET 上下文)被阻止等待任务完成。这就是导致您陷入僵局的原因。

我确实尝试在第一个异步方法上使用 .ConfigureAwait(false) 但没有任何成功。

为了避免使用ConfigureAwait(false) 的这种死锁,必须将其应用于every 方法中调用的every await。所以DoSomethingAsync 必须对每个await 使用它,DoSomethingAsync 调用的每个方法都必须对每个await 使用它(例如,Authenticate),这些方法调用的每个方法都必须对每个await 使用它(例如,PostAsync)等。请注意,在此处的末尾,您依赖于库代码,实际上 HttpClient 过去错过了其中的一些。

我发现如果我更改第一个版本[使用Task.Run],我可以使它工作。

是的。 Task.Run 将在线程池线程上执行其委托 任何上下文。所以这就是没有死锁的原因:awaits 都没有尝试在 ASP.NET 上下文中恢复。

为什么不以正确的方式使用它。 async void btn_Click 并等待 Class1.DoSomethingAsync() ?

不要将 Task.Run 与已经异步的方法一起使用,这会浪费线程。只需更改 button_click 事件处理程序的签名

这是正确的答案:不要阻塞异步代码。只需使用async void 事件处理程序并改用await

附: ASP.NET Core 不再具有 ASP.NET 上下文,因此您可以根据需要进行阻塞,而不必担心死锁。但是你当然不应该这样做,因为它的效率很低。

【讨论】:

  • 我部分不同意 正确 的答案...有时您需要从普通的旧异步代码调用异步方法,这种代码使用线程、锁和信号量,或者在你不想让async Task&lt;return&gt; 病毒一直感染到根部的长堆栈深处的代码中。
  • @LouisSomers:有a few hacks 可用于从同步代码调用异步代码,但没有适用于所有场景的解决方案。我认为您评论中的关键是“不想要” - 几乎总是可能以正确的方式做到这一点;只是人们不想要(或者,更常见的是,企业不会优先考虑工作)。
猜你喜欢
  • 2017-04-24
  • 1970-01-01
  • 2015-01-05
  • 2018-06-29
  • 1970-01-01
  • 2019-11-22
  • 2016-11-23
  • 1970-01-01
相关资源
最近更新 更多