Web Api + HttpClient:在asynchronous操作仍在等待时完成的asynchronous模块或处理程序

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

在我详细讨论之前,这里是应用程序的一般通信stream程:

  • 客户端代理1发出HTTP请求。
  • 代理1将HTTP请求的内容中继到代理2
  • 代理2将HTTP请求的内容中继到目标Web应用程序
  • 目标Web应用程序响应HTTP请求,并将响应stream式传输(分块传输)到代理2
  • 代理2将响应返回到代理1代理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; } 

间歇发生的错误是:

asynchronous模块或处理程序在asynchronous操作尚未完成时完成。

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

引发时,Visual Studio从不捕获exception。 但是可以在Global.asax Application_Error事件中捕获错误。 不幸的是,Exception没有堆栈跟踪。

代理应用程序托pipe在Azure Webangular色中。

任何帮助确定罪魁祸首将不胜感激。

您的问题是一个微妙的问题:您传递给PushStreamContentasync lambda被解释为async void (因为PushStreamContent构造函数仅将Action s作为参数)。 所以你的模块/处理程序完成和async void lambda的完成之间有一个竞争条件。

PostStreamContent检测到streamclosures并将其视为Task的结束(完成模块/处理程序),因此您只需确保没有async void方法在streamclosures后仍然可以运行。 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; } 

如果你想让你的代理扩展一点,我还build议摆脱所有的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一直到您的控制器级别,在这段时间内不会阻塞线程。

一个稍微简单的模型是,你可以直接使用HttpContents并在继电器内部传递它们。 我刚刚上传了一个示例,说明如何以asynchronous方式同时处理请求和响应,而不是以相对简单的方式缓冲内容:

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

重复使用相同的HttpClient实例也是有益的,因为这样可以在适当的情况下重用连接。

我想给在这里降落的人同样的错误添加一些智慧,但是你所有的代码看起来都很好。 查找从发生这种情况的所有调用树中传入函数的lambdaexpression式。

我得到了一个JavaScript JSON调用MVC 5.x控制器操作的这个错误。 我在堆栈上下所做的所有事情都被定义为async Task并使用await调用。

但是,使用Visual Studio的“设置下一个语句”function,我系统地跳过行来确定哪一个造成了它。 我一直深入到本地方法,直到我打电话到一个外部的NuGet包。 被调用的方法将一个Action作为参数,传入此Action的lambdaexpression式之前是async关键字。 正如Stephen Cleary在他的回答中指出的那样,这被视为MVC不喜欢的async void 。 幸运的是,软件包有*asynchronous版本的相同的方法。 切换到使用这些,以及一些下游调用相同的包解决了这个问题。

我意识到这不是一个新颖的解决scheme的问题,但我在这个线索几次在我的search试图解决这个问题,因为我以为我没有任何async voidasync <Action>调用,我想帮助其他人避免这种情况。