Blazor 服务器端异步死锁

Blazor server-side async deadlock

我有一个要从 blazor 组件调用的异步方法。我需要直接从组件调用它,而不是从它的生命周期挂钩(它运行良好的地方)调用它。

我不确定为什么我的案例有些有效而有些无效。

我知道在 ASP/UI context 线程中同步等待异步任务可能会导致死锁。但在这里,情况略有不同,因为 ConfigureAwait(false) 没有帮助。

我的假设是它与事实有关,在 Blazor server-side 中有多个可能的 UI 线程,所以当我设置 ConfigureAwait(false) 然后在大多数情况下其余的的工作将由不同但仍然 UI 线程处理。

有人可以解释一下那里发生了什么吗?

这是我的组件代码(简化版):

@page "/fetchdata"
@inject AsyncTaskService TaskService

//works
<p>Google request sync result: @TaskService.RequestGoogle()</p>

//deadlock
<p>Google request async result #1: @TaskService.RequestGoogleAsync().Result</p>

//deadlock
<p>Google request async result #2: @TaskService.RequestGoogleAsync().ConfigureAwait(false).GetAwaiter().GetResult()</p>

//works (because ThreadPoolThread performs requesting?)
<p>Google request async result #3: @(Task.Run(() => TaskService.RequestGoogleAsync()).Result)</p>

//can not compile
<p>Google request async result #4: @await TaskService.RequestGoogleAsync()</p>

AsyncTaskService:

public class AsyncTaskService
{
    private readonly HttpClient _httpClient;

    public AsyncTaskService(HttpClient httpClient)
    {
        this._httpClient = httpClient;
    }

    public string RequestGoogle()
    {
        var webResponse = _httpClient.GetAsync("https://www.google.com").Result;
        var result = webResponse.StatusCode.ToString();

        return result;
    }

    public async Task<string> RequestGoogleAsync()
    {
        var webResponse = await _httpClient.GetAsync("https://www.google.com");
        var result = webResponse.StatusCode.ToString();

        return result;
    }
}

I know that synchronously waiting for an async Task inside ASP/UI context thread may casue deadlock. But here, the situation is little different since ConfigureAwait(false) doesn't help.

实际上并没有什么不同。这是 exact same deadlock.

ConfigureAwait(false) 用作 blocking hack 的问题在于它需要应用于 every await 的传递闭包中从阻塞点调用的所有方法。因此,对于阻塞代码 TaskService.RequestGoogleAsync().GetAwaiter().GetResult(),这意味着 TaskService.RequestGoogleAsync 必须在每个 await 的每个方法上使用 ConfigureAwait(false) TaskService.RequestGoogleAsync 调用必须在每个 await 上使用 ConfigureAwait(false),这些方法调用的每个方法都必须在每个 [= 上使用 ConfigureAwait(false) 14=], 一直往下。包括您无法控制的代码(library/framework 代码)。如果您(或 library/framework 代码)错过了任何一个,那么您就有可能陷入僵局。

因此,要遍历:

//deadlock
<p>Google request async result #2: @TaskService.RequestGoogleAsync().ConfigureAwait(false).GetAwaiter().GetResult()</p>

^ 这里的 ConfigureAwait(false) 什么都不做,因为没有配置 await

//works (because ThreadPoolThread performs requesting?)
<p>Google request async result #3: @(Task.Run(() => TaskService.RequestGoogleAsync()).Result)</p>

^ 这个works 因为线程池没有可以捕获的“上下文”,所以避免了死锁。

//can not compile
<p>Google request async result #4: @await TaskService.RequestGoogleAsync()</p>

^ 这个会很理想。我建议您不要阻止异步代码(或 I/O)。

如果你不能在这里使用await,那么你应该在这个点之前使用await。我对 Blazor 不太熟悉,但对于 MVC,在确定模型时通常使用 await,然后将其传递给(同步)视图。在我看来,生命周期挂钩可能是更好的解决方案。