AsyncFunction().GetAwaiter().GetResult() 和 Task.Run(() => AsyncFunction).GetAwaiter().GetResult() 有什么区别?

Mar*_*ein 2 c# deadlock asynchronous async-await

我尝试理解之间的概念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();
    }
}
Run Code Online (Sandbox Code Playgroud)

调用该函数GetAwaiterGetResultOnFunction将导致死锁。然而,当我打电话时却GetAwaiterGetResultOnTask没有。

它们与功能之间的主要区别是什么?为什么调用时不会导致死锁Task.Run

Evk*_*Evk 7

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

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

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

GetResult然后,您可以通过执行从 . 返回的任务来阻止“UI”线程AsyncFunction。然后Task.Delay完成并将函数的其余部分发布到SynchronizationContextBlazor 来执行。但是,要做到这一点,Blazor 需要您刚刚阻止的相同 UI 线程,从而导致死锁。

当您调用时GetAwaiterGetResultOnTask-AsyncFunction不在 Blazor 的“UI”线程上运行 - 它在其他某个(线程池)线程上运行。SynchronizationContext为 null,之后的部分await Task.Delay将在某个线程池线程上运行,并且不需要“UI”线程来完成。那么就不存在僵局了。