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

Ada*_*dam 21 asp.net asynchronous httpwebrequest

在我的Web应用程序(ASP.NET)中,我有一块代码块,它使用HttpWebRequest来调用REST服务并继续执行.现在,它比我想要完成完整的Web请求需要更长的时间.问题是REST服务返回的内容无用.理想情况下,我想向REST服务发送异步Web请求,然后不等待响应.问题是我已经尝试过使用它了

request.BeginGetResponse(New AsyncCallback(AddressOf myFunc), Nothing)
Run Code Online (Sandbox Code Playgroud)

要启动异步请求而不是等待(我认为这将是异步请求的默认行为),它会在执行下一行代码之后继续执行回调函数BeginGetResponse.

我怀疑ASP.NET可能会在Web应用程序中将其转换为同步请求.我被引导相信这是因为有一个IAsyncResult result对象传递给回调函数,当我检查它的CompletedSynchronously属性时,它总是设置为true.

有没有人知道是否可以在ASP.NET Web应用程序中执行异步HttpWebRequest(没有等待),或者它是否总是转换为同步请求?

Bry*_*der 28

HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(myUrl);
//set up web request...
ThreadPool.QueueUserWorkItem(o=>{ myRequest.GetResponse(); });
Run Code Online (Sandbox Code Playgroud)

也被称为Fire-and-Forget.

  • 更新,.NET 4.0的更现代的方法可能是Task.Factory.StartNew(()=> myRequest.GetResponse()); (8认同)
  • 甚至Task.Run(()=> ...) (4认同)

Gri*_*air 13

(注意:我开始将其作为评论输入,但随着我在研究时获得了一些额外的知识,它变得足够大以获得答案)

1. ThreadPool.QueueUserWorkItem

@ram提供的前两个链接以及@Bryan Batchelders的回答都使用了备受争议的ThreadPool.QueueUserWorkItem.它在ASP.NET的上下文中存在争议,因为不正确的使用可能会使你的线程池匮乏,正如@Perhentian 链接所示.

2. BeginInvoke

然后我看了@ ram的第三个链接,它使用了BeginInvoke.从本质上讲,这似乎只是告诉一些代码在另一个线程运行.所以没有解决方案.

3. BeginGetResponse

现在回到@Perhentians 链接.它说明BeginGetResponse与实际线程有何不同,因为它使用IO完成端口(IOPC).所以你真正想要的是一个仍然使用这些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???
Run Code Online (Sandbox Code Playgroud)

所以,至少在一些后备场景中,线程仍然可能由于使用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);
Run Code Online (Sandbox Code Playgroud)

注意Timeout.Infinite.我还在调查是否有可能让套接字运行不执行等待的代码路径,但是现在,对我来说这似乎是不可能的.

根据我的结论:似乎没有简单的方法在Fire-And-Forget场景中使用IOCP.因此,除非你能让上面的套接字不等待BeginAccept的完成,否则你会遇到ThreadPool.