【问题标题】:HttpWebRequest async versus Begin/EndHttpWebRequest 异步与开始/结束
【发布时间】:2014-06-13 08:53:11
【问题描述】:

我有一个 Web API 项目,其控制器依次向第三方 REST 接口发出 Web 请求。我的控制器 CRUD 函数已标记为异步,但实现方式是我使用 HttpWebRequest 阻塞函数 GetRequestStream 和 GetResponse。

这是否合适或者我也应该使用 HttpWebRequest 的 Begin/End 方法。这似乎有点矫枉过正,因为它会异步一个已经异步的操作,还是允许更多并发的传出 Web 请求?

寻找传入和传出吞吐量的最佳解决方案。

【问题讨论】:

  • 你说你标记了那些方法async但是使用了阻塞方法?仅仅因为方法被标记为async 并不意味着它是异步的;特别是,如果您不使用await,那么它是同步的(检查您的警告消息)。
  • 是的,控制器被标记为异步,但实现使用同步 HttpWebRequest 调用,因为我的控制器正在等待 Task.Run(sync web request)。所以我想发生了什么是我收到了很多异步传入请求,然后所有这些请求都排队等待同步出站请求?我不明白的事情是,如果我使用 await HttpClient.SendAsync 那么某处肯定会阻塞等待响应,如果我有一个 4 核框但有 100 个请求,那么使用 SendAsync 如何帮助我的出站吞吐量?

标签: asp.net-web-api httpwebrequest


【解决方案1】:

控制器被标记为异步,但实现使用同步 HttpWebRequest 调用,因为我的控制器正在等待 Task.Run(sync web request)

想想请求中发生了什么。请求进来,ASP.NET 采用线程池线程来处理请求。控制器操作将工作排队到线程池(占用另一个线程),然后awaits 工作,释放原始请求线程。使用await 并没有获得任何好处,因为仍然有一个线程池线程阻塞了 Web 请求。

因此,您几乎应该从不在 ASP.NET 上使用Task.Run(或任何其他将工作排队到线程池的方法)。

这是否合适或者我也应该使用 HttpWebRequest 的 Begin/End 方法。这似乎有点矫枉过正,因为它会异步一个已经异步的操作

现在真的不是异步的;仍然有一个线程被阻塞。我将此称为“队列阻塞工作到线程池线程,然后等待它”技术假异步

适当的解决方法是使用为异步使用而设计的HttpClient;或者,您可以使用TaskFactory<T>.FromAsync to wrap the Begin/End methods into an awaitable task

但我不明白的是,如果我使用 await HttpClient.SendAsync,那么某处肯定会阻塞等待响应

不,不一定有什么东西在某处阻塞。正如我在博客中所描述的,在真正的异步场景中(即,不是假异步),there is no thread

【讨论】:

  • 好吧,我已经更改了代码,以便我的控制器等待我的 HttpClient 异步调用。最后,我还有另一个要求是使用显然不支持异步(使用 curl)的第 3 方 C++ 库,那么最好的方法是什么?
  • 如果它不是异步的并且你不能让它(真正)异步,那么就同步调用它。
【解决方案2】:

服务器上的异步独立于客户端的异步。

您可以使用WebRequest.GetResponseAsync 从客户端异步调用同步 Web API 方法,也可以调用异步 Web API 方法同步来自客户端WebRequest.GetResponse,或者你可以在双方都有异步,这可能是最好的方法。您不需要使用 Begin/End APM 风格的 WebRequest API,除非您在客户端上以 .NET 4.0 为目标。请改用基于 Task 的异步 API。

在任何一种情况下,当您的 Web API 控制器方法完全完成时,将向客户端发送一个完整的 HTTP 请求,无论它是实现为同步还是异步(基于Task)。

【讨论】:

    【解决方案3】:

    我建议您保留异步方法,并使用HttpClient 而不是 HttpWebRequest,因为它也支持异步调用。

    让我们看一个 CarsController 调用第三方服务的伪示例。

     public class CarsController : ApiController
        {
            [Route("cars/{id}")]
            public async Task<IHttpActionResult> Get(int id)
            {
    
                //Get a car by its id
    
                HttpClient client = new HttpClient();
    
                //Read the content directly
                string result = await client.GetStringAsync("http://www.google.com");
    
                //process the result returns from the thrid party REST service
    
                return Ok(result);
            }
    
    }
    

    如您所见,HttpClient支持通过GetXXXAsync()方法异步读取String/Stream/ByteArray。

    如果你想访问底层的响应内容,可以使用下面的代码来实现:

            [Route("responsecars/{id}")]
            public async Task<IHttpActionResult> GetResponseStream(int id)
            {
                HttpClient client = new HttpClient();
                HttpResponseMessage responseMessage =await client.GetAsync("http://www.google.com", HttpCompletionOption.ResponseContentRead);
    
                HttpContent content = responseMessage.Content;
    
                //using one of the ReadAsync methods
                string text =await content.ReadAsStringAsync();
    
                return Ok(text);
    
            }
    

    希望对您有所帮助。

    【讨论】:

    • 是的,但它有什么好处。如果我的控制器已经在对第 3 方接口进行异步调用,那么使这些调用也异步有什么额外的好处,还是实际上增加了额外的开销?
    • 您好,肯定有好处!因为如果调用需要很长时间,服务器可以释放你当前的线程来处理其他请求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-25
    • 2016-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多