在 ASPNET Core WebApi 上下文中解释 await Task.Yield

Explain await Task.Yield in an ASPNET Core WebApi context

来自documentation

可以使用await Task.Yield();在异步方法中强制该方法异步完成。如果存在当前同步上下文(SynchronizationContext 对象),这将 post 方法执行的其余部分返回到该上下文。但是,上下文将决定如何相对于其他可能待定的工作确定此工作的优先级。

所以我希望在 WebApi 中,我可以通过两种方式使用它

但我没有发现我的 .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,如我的博客所述。