异步/等待和Web API阻止执行

时间:2019-08-06 15:35:55

标签: c# asp.net async-await

我的REST API服务器中具有以下内容(使用OWIN):

    [ResponseType(typeof(Session))]
    [Route("api/v1/async")]
    public Session async_status()
    {
        AsyncStatus();
        return new Session() {...};
    }

    private async Task<int> AsyncStatus()
    {
        await SyncMethod();
        return 42;
    }

    private Task<int> SyncMethod()
    {
        CopyFileThatTakesALongTime(....);
        // OR
        Thread.Sleep(60000);
        return Task.FromResult(42);
    }

为什么会死,它会阻塞并且客户端无法获得所需的异步性。我希望在致电时

await SyncMethod();

调用者将被释放和解除阻塞,而其余的睡眠将被执行。我想念什么?

P.S:我可以通过替换以下内容轻松修复它:

AsyncStatus();

使用:

Task.Run(() => AsyncStatus() );

我不这样做的原因是因为阅读:

  
    

https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html

  

换句话说,如果像上面的文章中那样在Web服务器中使用Task.Run()不好,但是您不能使用async / await,那么解决方案是什么?

1 个答案:

答案 0 :(得分:2)

async方法仅在异步工作开始时变为异步。

也就是说,您的所有代码将同步运行,直到真正异步的启动。一旦开始一些真正的异步工作,就会在调用堆栈中返回Task,然后可以由调用方法等待(或不等待)。

这样做的含义是,如果您在异步工作之前执行大量同步工作,那么异步方法仍然可以阻止请求。您的示例完美地说明了这一点。让我们确切地分解一个线程在做什么:

  1. 开始运行async_status()
  2. 跳至AsyncStatus()
  3. await SyncMethod(),线程跳转到SyncMethod()
  4. 线程在Thread.Sleep(60000)处暂停60秒。
  5. SyncMethod()返回完整的Task
  6. 由于没有什么可等待的,AsyncStatus()同步继续并返回一个值。
  7. async_status()返回并因此返回请求。

Thread.Sleep(60000)可以替换为任何处理器密集型任务(或任何长时间运行的非异步任务),并且如果发生在该方法的第一个await之前,结果将是相同的。

这在Microsoft文档中的标题What happens in an async method下也有描述。注意点3:

  
      
  1. GetStringAsync中发生了某事,这会暂停其进度。也许它必须等待网站下载或其他阻止活动。为了避免阻塞资源,GetStringAsync将控制权交给其调用者AccessTheWebAsync
  2.   

控制仅在返回Task时返回到调用方法 ,这仅在异步任务实际启动时发生。在您的示例中,没有任何异步任务发生。如果您添加一个(甚至只是await Task.Delay(1)),则会看到不同的结果。例如,这将立即返回:

[ResponseType(typeof(Session))]
[Route("api/v1/async")]
public Session async_status()
{
    AsyncStatus();
    return new Session() {...};
}

private async Task<int> AsyncStatus()
{
    await SyncMethod();
    return 42;
}

private async Task<int> SyncMethod()
{
    await Task.Delay(1);
    Thread.Sleep(60000);
    return 42;
}

await Task.Delay(1)(或任何真正异步工作):

  1. SyncMethod()Task返回不完整的AsyncStatus()
  2. AsyncStatus()Task返回不完整的async_status()
  3. 没有等待Task,所以继续执行async_status()

要回答您的问题:

您问:

  

如果像上面的文章中那样在Web服务器中使用Task.Run()不好,但是您不能使用async / await,那么解决方案是什么?

您提到了Task.Run Etiquette文章。最后阅读他的结论:

  

请勿仅将其用于“为我的异步方法提供可使用的内容”。

您在这里的目的不仅是“提供可等待的东西”,而且是开始进行后台作业,而不是让客户端等到完成为止。不一样。

是的,您实际上可以使用Task.Run()。但是,正如Stephen Cleary的Fire and Forget on ASP.NET文章所述,这样做的问题在于ASP.NET无法跟踪该后台任务。因此,如果长时间运行,则回收应用程序池可能会立即终止该工作。他的文章描述了执行后台工作的更好方法,这些方法您不想阻止请求的返回。