【问题标题】:What's the difference between AsyncFunction().GetAwaiter().GetResult() and Task.Run(() => AsyncFunction).GetAwaiter().GetResult()?AsyncFunction().GetAwaiter().GetResult() 和 Task.Run(() => AsyncFunction).GetAwaiter().GetResult() 有什么区别?
【发布时间】:2021-10-04 13:11:40
【问题描述】:

我试图理解GetAwaiter().GetResult() 之间的概念。我知道如果可能的话不应该使用它,但在这种情况下我不能使用 async/await,所以我必须在同步问题中从异步函数中获取结果。

让我们考虑这个简单的 Blazor 服务器应用:

@page "/"

<button @onclick="GetAwaiterGetResultOnTask">Task GetAwaiter().GetResult()</button>
<button @onclick="GetAwaiterGetResultOnFunction">Function GetAwaiter().GetResult()</button>

<p>@_message</p>
@code {

    private string _message;
    private async Task<string> AsyncFunction(string message)
    {
        await Task.Delay(500);

        return message;
    }

    private void GetAwaiterGetResultOnTask()
    {
        _message = Task.Run(() => AsyncFunction("Message from GetAwaiterGetResultOnTask")).GetAwaiter().GetResult();
    }

    private void GetAwaiterGetResultOnFunction()
    {
        _message = AsyncFunction("Message from GetAwaiterGetResultOnFunction").GetAwaiter().GetResult();
    }
}

调用函数GetAwaiterGetResultOnFunction会导致死锁。但是,当我打电话给GetAwaiterGetResultOnTask 时,它没有。

这些函数之间的主要区别是什么?为什么在 Task.Run 上调用它不会导致死锁?

【问题讨论】:

  • 因为GetAwaiterGetResultOnFunction 运行on your thread,而GetAwaiterGetResultOnTask 运行在另一个上。
  • 顺便说一句,这两个电话都很糟糕。改为使方法异步。 Blazor Server 广泛使用异步方法是有原因的。 每个 调用修改状态和 UI 是从服务器到浏览器的异步调用。通过阻止您的方式,您浪费了可用于服务其他请求的线程
  • I cannot use async/await in this case 为什么?这是什么情况? Blazor,尤其是 Blazor Server,广泛使用 async,以至于 not 使用异步比使用它更难。为什么不使用异步事件回调? event handling docs 表明:button @onclick="UpdateHeading"private async Task UpdateHeading()
  • 这篇博文可能会解决这个问题:Don't Block on Async Code
  • ASP.NET Core 中尚不支持异步验证。这就是为什么我不能使用异步等待。

标签: c# asynchronous async-await deadlock


【解决方案1】:

await 知道一个叫做SynchronizationContext 的东西。如果存在这样的上下文(SynchronizationContext.Current 不为空),则在将await 传递给该上下文之后继续,因为它应该更好地知道如何处理它(除非您明确告诉不要这样做,使用await someTask.ConfigureAwait(continueOnCapturedContext: false)

大多数 UI 框架不喜欢同时从多个线程访问 UI,因为这通常会导致各种难以调试的问题。他们利用SynchronizationContext 强制执行单一流程。为此 - 他们通常将发布到 SynchronizationContext 的回调放在某种队列中,并在单个“UI”线程上一一执行。

Blazor 也这样做。 GetAwaiterGetResultOnFunction 是您的案例在该“UI”线程上执行,SynchronizationContext 可用。当执行到达AsyncFunction 内的await Task.Delay 时 - 捕获当前上下文,并注意到当 Task.Delay 完成时 - 函数的其余部分应发布到上下文以执行。

然后您通过对从AsyncFunction 返回的任务执行GetResult 来阻止“UI”线程。然后Task.Delay就完成了,剩下的函数会发布到Blazor的SynchronizationContext去执行。但是,要做到这一点,Blazor 需要您刚刚阻塞的同一个 UI 线程,从而导致死锁。

当您调用 GetAwaiterGetResultOnTask - AsyncFunction 不会在 Blazor 的“UI”线程上运行 - 它在其他一些(线程池)线程上运行。 SynchronizationContext 为空,await Task.Delay 之后的部分将在某个线程池线程上运行,并且不需要“UI”线程来完成。那么就没有死锁了。

【讨论】:

    猜你喜欢
    • 2016-07-25
    • 1970-01-01
    • 2016-04-05
    • 2016-12-24
    • 2020-05-10
    • 1970-01-01
    • 1970-01-01
    • 2014-07-25
    • 2020-07-31
    相关资源
    最近更新 更多