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
,然后将其传递给(同步)视图。在我看来,生命周期挂钩可能是更好的解决方案。
我有一个要从 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
,然后将其传递给(同步)视图。在我看来,生命周期挂钩可能是更好的解决方案。