在 ASPNET Core WebApi 上下文中解释 await Task.Yield
Explain await Task.Yield in an ASPNET Core WebApi context
可以使用await Task.Yield();在异步方法中强制该方法异步完成。如果存在当前同步上下文(SynchronizationContext 对象),这将 post 方法执行的其余部分返回到该上下文。但是,上下文将决定如何相对于其他可能待定的工作确定此工作的优先级。
所以我希望在 WebApi 中,我可以通过两种方式使用它
- 我的异步操作确实需要异步,我不希望内部实现return使用同步路径。
- 我的异步操作即将完成一些繁重的工作,但我不需要阻止对调用者的响应。作为一个过程,我将执行“放弃”给调用者,我很乐意稍后安排。
但我没有发现我的 .NET Core WebApi 发生了这种情况
[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
_logger.LogInformation("GET: Start DoSomethingAsync");
await DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
private async Task DoSomethingAsync()
{
_logger.LogDebug("DoSomethingAsync: Starting");
await Task.Yield();
await Task.Delay(1000);
_logger.LogDebug("DoSomethingAsync: Finsihed");
}
我希望先记录“End DoSomethingAsync”,然后再记录“DoSomethingAsync: Completed”。我知道 SynchronizationContext.Current
在这种情况下为空,但我的 DoSomethingAsync
不应该在 GetWeatherForecast 为 运行 后登录吗?
dbug: Microsoft.Extensions.Hosting.Internal.Host[2]
Hosting started
info: AsyncTestsApi.Controllers.WeatherForecastController[0]
Start DoSomethingAsync
dbug: AsyncTestsApi.Controllers.WeatherForecastController[0]
DoSomethingAsync: Starting
dbug: AsyncTestsApi.Controllers.WeatherForecastController[0]
DoSomethingAsync: Completed
info: AsyncTestsApi.Controllers.WeatherForecastController[0]
End DoSomethingAsync
I'm expecting "End DoSomethingAsync" to get logged first and "DoSomethingAsync: Completed" to come a second later
这是不正确的期望; await
这里:
_logger.LogInformation("GET: Start DoSomethingAsync");
await DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
意味着第 3 行不会发生,直到 DoSomethingAsync()
中的所有内容 完成如果需要 异步完成。您可以通过以下方式获得您想要的行为:
_logger.LogInformation("GET: Start DoSomethingAsync");
var pending = DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
await pending;
但是,这与在常规 non-async 代码中添加第二个线程基本相同 - 您现在在同一个请求上有两个活动的执行流。 可以有效,但如果不遵守thread-safety语义,也可能很危险。
in a WebApi, I can make use of this
我想不出 ASP.NET 上 await Task.Yield();
的有效用例。它会导致线程切换并且没有任何好处。
My async operation really needs to be async and I don't want the internal implementation to return using a synchronous path.
这绝对没有必要。在 ASP.NET,您没有“需要异步”的操作。如果是同步的,就让它同步。
My async operation is about to do some heavy work but I don't need to block the response to the caller. As a process, I'm "giving up" my execution to the caller and I'm happy to be scheduled later.
在 ASP.NET,仅当异步方法完成时才发送响应。所以 await Task.Yield();
(通常 await
)不会屈服于 browser/client;它让步给方法的调用者(或者 ASP.NET 运行时线程池,如果它被 ASP.NET 调用)。有关详细信息,请参阅 this article。
如果您需要早 return,那么您需要 proper distributed architecture,如我的博客所述。
可以使用await Task.Yield();在异步方法中强制该方法异步完成。如果存在当前同步上下文(SynchronizationContext 对象),这将 post 方法执行的其余部分返回到该上下文。但是,上下文将决定如何相对于其他可能待定的工作确定此工作的优先级。
所以我希望在 WebApi 中,我可以通过两种方式使用它
- 我的异步操作确实需要异步,我不希望内部实现return使用同步路径。
- 我的异步操作即将完成一些繁重的工作,但我不需要阻止对调用者的响应。作为一个过程,我将执行“放弃”给调用者,我很乐意稍后安排。
但我没有发现我的 .NET Core WebApi 发生了这种情况
[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
_logger.LogInformation("GET: Start DoSomethingAsync");
await DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
private async Task DoSomethingAsync()
{
_logger.LogDebug("DoSomethingAsync: Starting");
await Task.Yield();
await Task.Delay(1000);
_logger.LogDebug("DoSomethingAsync: Finsihed");
}
我希望先记录“End DoSomethingAsync”,然后再记录“DoSomethingAsync: Completed”。我知道 SynchronizationContext.Current
在这种情况下为空,但我的 DoSomethingAsync
不应该在 GetWeatherForecast 为 运行 后登录吗?
dbug: Microsoft.Extensions.Hosting.Internal.Host[2]
Hosting started
info: AsyncTestsApi.Controllers.WeatherForecastController[0]
Start DoSomethingAsync
dbug: AsyncTestsApi.Controllers.WeatherForecastController[0]
DoSomethingAsync: Starting
dbug: AsyncTestsApi.Controllers.WeatherForecastController[0]
DoSomethingAsync: Completed
info: AsyncTestsApi.Controllers.WeatherForecastController[0]
End DoSomethingAsync
I'm expecting "End DoSomethingAsync" to get logged first and "DoSomethingAsync: Completed" to come a second later
这是不正确的期望; await
这里:
_logger.LogInformation("GET: Start DoSomethingAsync");
await DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
意味着第 3 行不会发生,直到 DoSomethingAsync()
中的所有内容 完成如果需要 异步完成。您可以通过以下方式获得您想要的行为:
_logger.LogInformation("GET: Start DoSomethingAsync");
var pending = DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
await pending;
但是,这与在常规 non-async 代码中添加第二个线程基本相同 - 您现在在同一个请求上有两个活动的执行流。 可以有效,但如果不遵守thread-safety语义,也可能很危险。
in a WebApi, I can make use of this
我想不出 ASP.NET 上 await Task.Yield();
的有效用例。它会导致线程切换并且没有任何好处。
My async operation really needs to be async and I don't want the internal implementation to return using a synchronous path.
这绝对没有必要。在 ASP.NET,您没有“需要异步”的操作。如果是同步的,就让它同步。
My async operation is about to do some heavy work but I don't need to block the response to the caller. As a process, I'm "giving up" my execution to the caller and I'm happy to be scheduled later.
在 ASP.NET,仅当异步方法完成时才发送响应。所以 await Task.Yield();
(通常 await
)不会屈服于 browser/client;它让步给方法的调用者(或者 ASP.NET 运行时线程池,如果它被 ASP.NET 调用)。有关详细信息,请参阅 this article。
如果您需要早 return,那么您需要 proper distributed architecture,如我的博客所述。