AsyncFunction().GetAwaiter().GetResult() 和 Task.Run(() => AsyncFunction).GetAwaiter().GetResult() 有什么区别?
What's the difference between AsyncFunction().GetAwaiter().GetResult() and Task.Run(() => AsyncFunction).GetAwaiter().GetResult()?
我试着理解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
上调用时不会导致死锁?
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” 线程来完成。那么就没有死锁了。
我试着理解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
上调用时不会导致死锁?
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” 线程来完成。那么就没有死锁了。