首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Web + HttpClient:异步模块或处理程序在异步操作仍处于挂起状态时完成

Web + HttpClient:异步模块或处理程序在异步操作仍处于挂起状态时完成
EN

Stack Overflow用户
提问于 2013-02-25 12:38:38
回答 3查看 48.8K关注 0票数 49

我正在编写一个使用HTTP代理一些ASP.NET请求的应用程序,我正在努力找出一个间歇性错误的来源。这看起来像是一种竞争状况。但我不能完全确定。

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

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

代理应用程序是使用.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角色中。

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-02-25 23:15:31

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

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直到您的控制器级别,在此期间不会阻塞线程。

票数 67
EN

Stack Overflow用户

发布于 2017-07-27 05:41:53

我想为其他在这里遇到同样错误的人补充一些智慧,但你的所有代码看起来都很好。在调用树中查找传递到函数中的任何lambda表达式。

在对MVC5.x控制器操作的JavaScript JSON调用中,我得到了这个错误。我在堆栈上下执行的所有操作都是使用await定义async Task和调用的。

但是,使用Visual Studio的"Set next statement“功能,我系统地跳过了几行,以确定是哪一行引起的。我一直深入研究本地方法,直到我对外部NuGet包进行了调用。被调用的方法接受一个Action作为参数,为该操作传入的lambda表达式前面有async关键字。正如Stephen Cleary在上面的回答中指出的那样,这被视为async void,这是MVC不喜欢的。幸运的是,包中有*个相同方法的异步版本。切换到使用这些,以及一些下游调用相同的包解决了这个问题。

我意识到这不是一个解决问题的新方法,但在我试图解决这个问题的搜索中,我几次忽略了这个主题,因为我认为我没有任何async voidasync <Action>调用,我想帮助其他人避免这种情况。

票数 5
EN

Stack Overflow用户

发布于 2013-02-28 13:48:05

一个稍微简单一点的模型是,您实际上可以直接使用HttpContents,并在中继中传递它们。我刚刚上传了一个示例,演示了如何在不以相对简单的方式缓冲内容的情况下异步地依赖请求和响应:

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

重用相同的HttpClient实例也是有益的,因为这允许您在适当的地方重用连接。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15060214

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档