Web Api + HttpClient:异步模块或处理程序已完成,而异步操作仍待处理 [英] Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending

查看:45
本文介绍了Web Api + HttpClient:异步模块或处理程序已完成,而异步操作仍待处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个使用 ASP.NET Web API 代理一些 HTTP 请求的应用程序,我正在努力确定间歇性错误的来源.这似乎是一种竞争条件......但我并不完全确定.

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 race condition... but I'm not entirely sure.

在我详细介绍之前,这里是应用程序的一般通信流程:

Before I go into detail here is the general communication flow of the application:

  • 客户端代理 1 发出 HTTP 请求.
  • 代理 1 将 HTTP 请求的内容中继到代理 2
  • 代理 2 将 HTTP 请求的内容中继到目标 Web 应用程序
  • 目标 Web 应用响应 HTTP 请求并将响应流式传输(分块传输)到代理 2
  • 代理 2 将响应返回给 代理 1,后者又响应原始调用客户端.
  • Client makes a HTTP request to Proxy 1.
  • Proxy 1 relays the contents of the HTTP request to Proxy 2
  • Proxy 2 relays the contents of the HTTP request to the Target Web Application
  • Target Web App responds to the HTTP request and the response is streamed (chunked transfer) to Proxy 2
  • Proxy 2 returns the response to Proxy 1 which in turn responds to the original calling Client.

代理应用程序是使用 .NET 4.5 在 ASP.NET Web API RTM 中编写的.执行中继的代码如下所示:

The Proxy applications are written in ASP.NET Web API RTM using .NET 4.5. The code to perform the relay looks like so:

//Controller entry point.
public HttpResponseMessage Post()
{
    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 = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;

        var returnMessage = BuildResponse(relayResult);
        return returnMessage;
    }
}

private static HttpRequestMessage BuildRelayHttpRequest(HttpRequestMessage incomingRequest)
{
    var requestUri = BuildRequestUri();
    var relayRequest = new HttpRequestMessage(incomingRequest.Method, requestUri);
    if (incomingRequest.Method != HttpMethod.Get && incomingRequest.Content != null)
    {
       relayRequest.Content = incomingRequest.Content;
    }

    //Copies all safe HTTP headers (mainly content) to the relay request
    CopyHeaders(relayRequest, incomingRequest);
    return relayRequest;
}

private static HttpRequestMessage BuildResponse(HttpResponseMessage responseMessage)
{
    var returnMessage = Request.CreateResponse(responseMessage.StatusCode);
    returnMessage.ReasonPhrase = responseMessage.ReasonPhrase;
    returnMessage.Content = CopyContentStream(responseMessage);

    //Copies all safe HTTP headers (mainly content) to the response
    CopyHeaders(returnMessage, responseMessage);
}

private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
    var content = new PushStreamContent(async (stream, context, transport) =>
            await sourceContent.Content.ReadAsStreamAsync()
                            .ContinueWith(t1 => t1.Result.CopyToAsync(stream)
                                .ContinueWith(t2 => stream.Dispose())));
    return content;
}

间歇性出现的错误是:

异步模块或处理程序已完成,而异步操作仍处于挂起状态.

An asynchronous module or handler completed while an asynchronous operation was still pending.

此错误通常发生在对代理应用程序的前几次请求中,之后不再出现错误.

This error usually occurs on the first few requests to the proxy applications after which the error is not seen again.

Visual Studio 永远不会在抛出异常时捕获异常.但是该错误可以在 Global.asax Application_Error 事件中捕获.不幸的是,异常没有堆栈跟踪.

Visual Studio never catches the Exception when thrown. But the error can be caught in the Global.asax Application_Error event. Unfortunately the Exception has no Stack Trace.

代理应用程序托管在 Azure Web 角色中.

The proxy applications are hosted in Azure Web Roles.

如有任何帮助确定罪魁祸首,我们将不胜感激.

Any help identifying the culprit would be appreciated.

推荐答案

您的问题很微妙:您传递给 PushStreamContentasync lambda 正在被解释作为 async void(因为 PushStreamContent 构造函数 只接受 Actions 作为参数).因此,您的模块/处理程序完成与 async void lambda 的完成之间存在竞争条件.

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 检测流关闭并将其视为其 Task 的结束(完成模块/处理程序),因此您只需要确保没有 async void 在流关闭后仍然可以运行的方法.async Task 方法没问题,所以这应该解决它:

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<Stream, Task> 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;
}

如果你想让你的代理扩展得更好,我还建议你去掉所有的 Result 调用:

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<HttpResponseMessage> 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;
  }
}

您以前的代码会为每个请求阻塞一个线程(直到收到标头);通过使用 async 一直到您的控制器级别,您将不会在此期间阻塞线程.

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.

这篇关于Web Api + HttpClient:异步模块或处理程序已完成,而异步操作仍待处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆