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

时间:2023-01-23 18:13:48

In my web application (ASP.NET) I have a block of code that uses HttpWebRequest to make a call to a REST service and continue execution. Right now it's taking longer than I would like to complete the full web request. The thing is that what the REST service returns isn't useful. Ideally I would like to send an Async web request to the REST service and then NOT wait for a response. The problem is that I've tried it out using

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

request.BeginGetResponse(New AsyncCallback(AddressOf myFunc), Nothing)

To start an async request and instead of NOT waiting (which I would assume would be the default behavior of an async request) it continuously executes the callback function before executing the next line of code after BeginGetResponse.

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

I'm suspecting that ASP.NET may convert it to a sync request when it's within a web application. I'm led to believe this because there's a IAsyncResult result object that is passed into the callback function and when I examine its CompletedSynchronously property it's always set to true.

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

Does anyone know if it's possible to do an async HttpWebRequest (with no wait) from within an ASP.NET web application or is it always converted to a synchronous request?

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

3 个解决方案

#1


14  

you are probably looking at "fire and forget" pattern. Here are some links.

你可能正在看“火与忘”的模式。这是一些链接。

http://weblogs.asp.net/albertpascual/archive/2009/05/14/fire-and-forget-class-for-asp-net.aspx

http://weblogs.asp.net/albertpascual/archive/2009/05/14/fire-and-forget-class-for-asp-net.aspx

http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

http://www.eggheadcafe.com/articles/20060727.asp

http://www.eggheadcafe.com/articles/20060727.asp

hope this helps

希望这可以帮助

#2


27  

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

Also known as Fire-and-Forget.

也被称为Fire-and-Forget。

#3


13  

(Note: I started to enter this as a comment, but as I gained some extra knowledge while researching, it grew big enough for an answer)

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

1. ThreadPool.QueueUserWorkItem

The first two links that @ram provides, as well as @Bryan Batchelders answer make use of the controversial ThreadPool.QueueUserWorkItem. It's controversial within the context of ASP.NET because uncareful use may starve your thread pool, as @Perhentian link shows.

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

2. BeginInvoke

Then I had a look at @ram's third link, which makes use of BeginInvoke. In its essence, this just seems to tell some code to run on another thread too. So no resolution here.

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

3. BeginGetResponse

Now back to @Perhentians link. It states how BeginGetResponse is somewhat different from an actual thread, because it uses IO Completion Ports (IOPCs). So what you're actually looking for, is a solution which still uses these IOPCs without creating an extra thread.

现在回到@Perhentians链接。它说明BeginGetResponse与实际线程有何不同,因为它使用IO完成端口(IOPC)。所以你真正想要的是一个仍然使用这些IOPC而不创建额外线程的解决方案。

Now, in order to see how .NET does this, I tried to dig into HttpWebRequest.BeginGetResponse, which is really a rag of internal calls. It goes like:

现在,为了看看.NET是如何做到这一点的,我试图深入研究HttpWebRequest.BeginGetResponse,这实际上是一堆内部调用。它像:

  1. HttpWebRequest.BeginGetResponse
  2. HttpWebRequest.BeginGetResponse
  3. ServicePoint.SubmitRequest
  4. ServicePoint.SubmitRequest
  5. Connection.SubmitRequest
  6. Connection.SubmitRequest
  7. Two options:
    • If the connection is clear: Connection.StartRequest
    • 如果连接是明确的:Connection.StartRequest
    • Otherwise: Connection.WaitList.Add(request)
    • 否则:Connection.WaitList.Add(request)
  8. 两个选项:如果连接是明确的:Connection.StartRequest否则:Connection.WaitList.Add(request)

A. WaitList

First lets consider option 2: the said WaitList gets processed when a connection is done with a previous request. Taking a look at the ConnectStream involved in this whole chain shows a ThreadPool.QueueUserWorkItem with the remark:

首先考虑选项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???

So, at least in some fallback scenario's, threads can still inadvertently get spawned by the framework, by just using BeginGetResponse!

因此,至少在一些后备场景中,线程仍然可能由于使用BeginGetResponse而无意中被框架衍生出来!

B. Connection.StartRequest

Now, we still have the scenario where the connection is clear. The callback is installed by System.Net.Sockets.Socket.BeginConnect and actually invoked by BeginAccept. Some more digging reveals a call to ThreadPool.UnsafeRegisterWaitForSingleObject whose result is used to wait for.

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

Conclusion

Finally, we can tell what's actually going on when doing a BeginGetResponse:

最后,我们可以告诉您在执行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);

Note the Timeout.Infinite. I'm still investigating whether it is possible to get the socket running a codepath that does not perform the wait, but for now, it looks impossible to me.

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

As goes for my conclusion: there seems to be no easy way to use IOCPs in a Fire-And-Forget scenario. So unless you can get the above socket to not wait for completion on BeginAccept, you're stuck with ThreadPool.

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

#1


14  

you are probably looking at "fire and forget" pattern. Here are some links.

你可能正在看“火与忘”的模式。这是一些链接。

http://weblogs.asp.net/albertpascual/archive/2009/05/14/fire-and-forget-class-for-asp-net.aspx

http://weblogs.asp.net/albertpascual/archive/2009/05/14/fire-and-forget-class-for-asp-net.aspx

http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

http://www.eggheadcafe.com/articles/20060727.asp

http://www.eggheadcafe.com/articles/20060727.asp

hope this helps

希望这可以帮助

#2


27  

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

Also known as Fire-and-Forget.

也被称为Fire-and-Forget。

#3


13  

(Note: I started to enter this as a comment, but as I gained some extra knowledge while researching, it grew big enough for an answer)

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

1. ThreadPool.QueueUserWorkItem

The first two links that @ram provides, as well as @Bryan Batchelders answer make use of the controversial ThreadPool.QueueUserWorkItem. It's controversial within the context of ASP.NET because uncareful use may starve your thread pool, as @Perhentian link shows.

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

2. BeginInvoke

Then I had a look at @ram's third link, which makes use of BeginInvoke. In its essence, this just seems to tell some code to run on another thread too. So no resolution here.

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

3. BeginGetResponse

Now back to @Perhentians link. It states how BeginGetResponse is somewhat different from an actual thread, because it uses IO Completion Ports (IOPCs). So what you're actually looking for, is a solution which still uses these IOPCs without creating an extra thread.

现在回到@Perhentians链接。它说明BeginGetResponse与实际线程有何不同,因为它使用IO完成端口(IOPC)。所以你真正想要的是一个仍然使用这些IOPC而不创建额外线程的解决方案。

Now, in order to see how .NET does this, I tried to dig into HttpWebRequest.BeginGetResponse, which is really a rag of internal calls. It goes like:

现在,为了看看.NET是如何做到这一点的,我试图深入研究HttpWebRequest.BeginGetResponse,这实际上是一堆内部调用。它像:

  1. HttpWebRequest.BeginGetResponse
  2. HttpWebRequest.BeginGetResponse
  3. ServicePoint.SubmitRequest
  4. ServicePoint.SubmitRequest
  5. Connection.SubmitRequest
  6. Connection.SubmitRequest
  7. Two options:
    • If the connection is clear: Connection.StartRequest
    • 如果连接是明确的:Connection.StartRequest
    • Otherwise: Connection.WaitList.Add(request)
    • 否则:Connection.WaitList.Add(request)
  8. 两个选项:如果连接是明确的:Connection.StartRequest否则:Connection.WaitList.Add(request)

A. WaitList

First lets consider option 2: the said WaitList gets processed when a connection is done with a previous request. Taking a look at the ConnectStream involved in this whole chain shows a ThreadPool.QueueUserWorkItem with the remark:

首先考虑选项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???

So, at least in some fallback scenario's, threads can still inadvertently get spawned by the framework, by just using BeginGetResponse!

因此,至少在一些后备场景中,线程仍然可能由于使用BeginGetResponse而无意中被框架衍生出来!

B. Connection.StartRequest

Now, we still have the scenario where the connection is clear. The callback is installed by System.Net.Sockets.Socket.BeginConnect and actually invoked by BeginAccept. Some more digging reveals a call to ThreadPool.UnsafeRegisterWaitForSingleObject whose result is used to wait for.

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

Conclusion

Finally, we can tell what's actually going on when doing a BeginGetResponse:

最后,我们可以告诉您在执行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);

Note the Timeout.Infinite. I'm still investigating whether it is possible to get the socket running a codepath that does not perform the wait, but for now, it looks impossible to me.

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

As goes for my conclusion: there seems to be no easy way to use IOCPs in a Fire-And-Forget scenario. So unless you can get the above socket to not wait for completion on BeginAccept, you're stuck with ThreadPool.

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