Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending

后端 未结 3 2031
悲哀的现实
悲哀的现实 2020-12-07 14:15

I\'m writing an application that proxies some HTTP requests using the ASP.NET Web API and I am struggling to identify the source of an intermittent error. It seems like a ra

3条回答
  •  执笔经年
    2020-12-07 14:24

    Your problem is a subtle one: the async lambda you're passing to PushStreamContent is being interpreted as an async void (because the PushStreamContent constructor only takes Actions as parameters). So there's a race condition between your module/handler completing and the completion of that async void lambda.

    PostStreamContent detects the stream closing and treats that as the end of its Task (completing the module/handler), so you just need to be sure there's no async void methods that could still run after the stream is closed. async Task methods are OK, so this should fix it:

    private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
    {
      Func copyStreamAsync = async stream =>
      {
        using (stream)
        using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
        {
          await sourceStream.CopyToAsync(stream);
        }
      };
      var content = new PushStreamContent(stream => { var _ = copyStreamAsync(stream); });
      return content;
    }
    

    If you want your proxies to scale a bit better, I also recommend getting rid of all the Result calls:

    //Controller entry point.
    public async Task PostAsync()
    {
      using (var client = new HttpClient())
      {
        var request = BuildRelayHttpRequest(this.Request);
    
        //HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
        //As it begins to filter in.
        var relayResult = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    
        var returnMessage = BuildResponse(relayResult);
        return returnMessage;
      }
    }
    

    Your former code would block one thread for each request (until the headers are received); by using async all the way up to your controller level, you won't block a thread during that time.

提交回复
热议问题