(注意:我最开始想把这个作为评论提交,但在研究过程中获得了额外的知识,结果变得足够长,可以作为一个回答。)
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,它真的是一堆内部调用。大致流程如下:
- HttpWebRequest.BeginGetResponse
- ServicePoint.SubmitRequest
- Connection.SubmitRequest
- 两个选项:
- 如果连接已明确: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上不等待完成,否则只能使用线程池。
ThreadPool
,它会更频繁地被使用 :) 现在这使得"fire and forget"变得非常简单... +1 - Piotr Kula