在Web应用程序中使用无需等待的异步HttpWebRequest

22
在我的Web应用程序(ASP.NET)中,我有一段代码块使用HttpWebRequest来调用REST服务并继续执行。现在它花费的时间比我想象的要长,以完成整个Web请求。问题在于REST服务返回的内容是没有用的。理想情况下,我想发送一个异步Web请求到REST服务,然后不等待响应。但问题是,我已经试过了,使用...
request.BeginGetResponse(New AsyncCallback(AddressOf myFunc), Nothing)

如何开始一个异步请求并且让它在 BeginGetResponse 之后执行下一行代码之前持续执行回调函数,而不是等待(我认为这是异步请求的默认行为)。

我怀疑当异步请求在Web应用程序中时,ASP.NET会将其转换为同步请求。我之所以有这种想法,是因为回调函数中会传递一个IAsyncResult result对象,并且当我检查它的CompletedSynchronously属性时,它总是设置为true。

是否有人知道是否可以在ASP.NET Web应用程序中进行无需等待的异步HttpWebRequest,还是它总是被转换为同步请求?

3个回答

32
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(myUrl);
//set up web request...
ThreadPool.QueueUserWorkItem(o=>{ myRequest.GetResponse(); });

也被称为“Fire-and-Forget”(点燃即走)。


现在我知道了ThreadPool,它会更频繁地被使用 :) 现在这使得"fire and forget"变得非常简单... +1 - Piotr Kula
8
更新,适用于.NET 4.0的更现代方法可能是Task.Factory.StartNew(() => myRequest.GetResponse()); - RJB
4
甚至可以使用Task.Run(() => ...)。 - Erti-Chris Eelmaa

18

1
这太棒了!特别是那个 haacked.com 博客将其简化为一行代码。我可能会在应用程序的其他部分中也使用这种模式。 - Adam
我想要确保在IIS中使用ThreadPool.QueueUserWorkItem是安全的。(相关链接:https://dev59.com/f3M_5IYBdhLWcg3wjj-r) - Perhentian

14

(注意:我最开始想把这个作为评论提交,但在研究过程中获得了额外的知识,结果变得足够长,可以作为一个回答。)

1. ThreadPool.QueueUserWorkItem

@ram提供的前两个链接以及@Bryan Batchelders的回答都使用了备受争议的ThreadPool.QueueUserWorkItem。在ASP.NET环境中,不小心使用可能会耗尽线程池,就像@Perhentian 在这里所示。

2. BeginInvoke

然后我看了一下@ram提供的第三个链接,它使用了BeginInvoke。本质上,这只是告诉一些代码也要在另一个线程上运行。所以这里没有解决方案。

3. BeginGetResponse

现在回到@Perhentian的链接。它说明BeginGetResponse与实际线程有所不同,因为它使用了IO完成端口 (IOCPs)。因此,您实际上要寻找的是一种在不创建额外线程的情况下仍然使用这些IOPC的解决方案。

现在,为了看到.NET是如何做到这一点的,我尝试深入研究HttpWebRequest.BeginGetResponse,它真的是一堆内部调用。大致流程如下:

  1. HttpWebRequest.BeginGetResponse
  2. ServicePoint.SubmitRequest
  3. Connection.SubmitRequest
  4. 两个选项:
    • 如果连接已明确:Connection.StartRequest
    • 否则:Connection.WaitList.Add(request)

A. WaitList

首先让我们考虑选项2:当连接完成上一个请求时,会处理所述WaitList。查看涉及整个链的ConnectStream显示了一个ThreadPool.QueueUserWorkItem并带有备注:

// otherwise we queue a work item to parse the chunk
// Consider: Will we have an issue of thread dying off
// if we make a IO Read from that thread???

因此,至少在某些备用方案中,只需使用BeginGetResponse即可意外生成线程。

B. Connection.StartRequest

现在,我们仍然有一个连接是清晰的场景。回调是由System.Net.Sockets.Socket.BeginConnect安装的,实际上由BeginAccept调用。进一步挖掘揭示了对ThreadPool.UnsafeRegisterWaitForSingleObject的调用,其结果用于等待。

结论

最终,我们可以看出当执行BeginGetResponse时到底发生了什么:

// 1. Connecting a socket
UnsafeNclNativeMethods.OSSOCK.WSAConnect(m_handle)

// 2. Registering the callback
m_RegisteredWait = ThreadPool.UnsafeRegisterWaitForSingleObject(m_AsyncEvent, s_RegisteredWaitCallback, this, Timeout.Infinite, true);

// 3. Waiting for the socket to complete
UnsafeNclNativeMethods.OSSOCK.WSAEventSelect(m_handle, m_AsyncEvent.SafeWaitHandle, blockEventBits);

请注意Timeout.Infinite。我仍在调查是否可能使套接字运行不执行等待的代码路径,但目前看来这似乎是不可能的。

至于我的结论:在Fire-And-Forget场景中似乎没有简单的方法使用IOCPs。因此,除非你能让上述套接字在BeginAccept上不等待完成,否则只能使用线程池。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接