网络API +的HttpClient:异步模块或处理程序,而异步操作仍有待完成(Web Api +

2019-07-20 11:15发布

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

我细讲之前这里是应用程序的常规通信流程:

  • 客户端发出一个HTTP请求到代理服务器1。
  • 代理1中继HTTP请求的内容,以代理2
  • 代理2个继电器HTTP请求的内容,目标网络应用
  • 目标Web应用程序响应该HTTP请求和响应被流传输(块传输)到代理2
  • 代理2返回到代理1的响应继而响应原始调用客户端

代理服务器应用程序是使用.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;
}

发生间歇性的错误是:

异步模块或处理程序而异步操作仍有待完成。

这个错误通常发生在最初的几个请求后未再见过错误的代理应用程序。

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

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

任何标识的罪魁祸首帮助将不胜感激。

Answer 1:

你的问题是一个微妙的:在async你传递至拉姆达PushStreamContent被解释为async void (因为PushStreamContent构造只需要Action S作为参数)。 因此,有你之间的模块/处理程序完成的,这个完成的竞争条件async void拉姆达。

PostStreamContent检测到流关闭和对待,作为其结束Task (完成模块/处理),所以你只需要确保没有async void流被关闭后仍可运行的方法。 async Task方式都OK,所以这应该修复它:

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

您的代码前将阻塞一个线程对每个请求(直到接收到所述报头); 通过使用async一路攀升到你的控制器级,你会不会阻止在这段时间线程。



Answer 2:

稍微简单的模型是可以真正地直接使用HttpContents并通过他们周围继电器内部。 我刚刚上传的样本说明了如何依靠请求和响应异步和无缓冲以相对简单的方式内容:

http://aspnet.codeplex.com/SourceControl/changeset/view/7ce67a547fd0#Samples/WebApi/RelaySample/ReadMe.txt

这也有利于重复使用同一个HttpClient的实例,因为这可以让你在适当情况下重用连接。



Answer 3:

我想补充一些智慧,谁比谁有同样的错误在这里降落,但所有的代码似乎罚款。 寻找从那里发生这种情况传递给函数在整个调用树的任何lambda表达式。

我是在一个JavaScript JSON调用的MVC 5.x的控制器动作得到这个错误。 一切,我在做向上和向下的堆栈定义async Task ,并使用名为await

但是,使用Visual Studio的“设置下一条语句”功能,我系统地跳过行来决定哪一个造成的。 我不停地向下钻取到本地方法,直到我到了一个电话到外部NuGet包。 所调用的方法采取了Action作为参数,并为这个动作通过在lambda表达受前面async关键字。 正如斯蒂芬·克利里在他的回答上面所指出的,这被视为一个async void ,这MVC不喜欢。 幸运的是,该包装具有相同的方法*异步版本。 切换到使用这些,与一些下游调用同一包沿着固定的问题。

我知道这是不是一个新的解决问题的方法,但我经过这个线程几次在我的搜索尝试,因为我觉得我没有任何要解决这个问题async voidasync <Action>电话,我想帮助别人避免这种情况。



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