c# - Web API + HttpClient : An asynchronous module or handler completed while an asynchronous operation was still pending

标签 c# asp.net asp.net-web-api dotnet-httpclient

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

在详细介绍之前,先介绍应用程序的一般通信流程:

  • ClientProxy 1 发出 HTTP 请求。
  • 代理 1 将 HTTP 请求的内容转发给代理 2
  • 代理 2 将 HTTP 请求的内容中继到目标 Web 应用程序
  • 目标 Web 应用 响应 HTTP 请求并将响应流式传输(分 block 传输)到代理 2
  • Proxy 2 将响应返回给 Proxy 1,后者又响应原始调用 Client

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

//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.

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

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

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

如果能帮助识别罪魁祸首,我们将不胜感激。

最佳答案

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

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

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 调用:

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

您以前的代码会为每个请求阻塞一个线程(直到收到 header );通过一直使用 async 直到您的 Controller 级别,在此期间您不会阻塞线程。

关于c# - Web API + HttpClient : An asynchronous module or handler completed while an asynchronous operation was still pending,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15060214/

相关文章:

c# - 将查询字符串数组参数转换为字典值

c# - .NET 4.0 中的 MembershipProvider

c# - 使用 C# 突出显示 TextBox/Label/RichTextBox 中的文本

javascript - 将 javascript 代码移动到设计 View 不起作用。仅代码隐藏 Attributes.Add ("onclick"有效。困惑

c# - 自托管webapi时如何设置http请求队列长度?

asp.net-mvc - 使用WEB API时,如何从POST的HttpResponseMessage中提取内容?

c# - 两个 DataGridView;排列列

c# - 如何在 ASP.NET 中监听 IIS 关闭事件

asp.net - 在 Web 窗体应用程序中包含 MVC 3 View

c# - 仅在ASP.NET MVC页面上显示从数据库进行数据相减操作的结果