Web Api + HttpClient:异步模块或处理程序在异步操作仍处于待处理状态时已完成(Web Api + HttpClient: An asynchronous module or hand

编程入门 行业动态 更新时间:2024-10-25 10:22:57
Web Api + HttpClient:异步模块或处理程序在异步操作仍处于待处理状态时已完成(Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending)

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

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

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

间歇性发生的错误是:

异步模块或处理程序在异步操作仍在等待时完成。

这个错误通常发生在代理应用程序的前几个请求之后,再次看不到错误。

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

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

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

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:

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.

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

The error that occurs intermittently is:

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

The proxy applications are hosted in Azure Web Roles.

Any help identifying the culprit would be appreciated.

最满意答案

您的问题是一个微妙的问题:您传递给PushStreamContent的async lambda被解释为一个async void (因为PushStreamContent构造函数仅将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; } }

您以前的代码将阻止每个请求的一个线程(直到接收到标头); 通过使用async一直到你的控制器级别,你不会阻止一个线程在那段时间。

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

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

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.

更多推荐

本文发布于:2023-04-29 12:11:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1336282.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模块   状态   操作   程序   HttpClient

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!