编写者:郑昀@Ultrapower 20050406
其实微软有一篇《异步 HttpWebRequest、接口实现及其他》对此种现象解释得非常清楚,我这边只是做一个笔记。
最常见的就是使用HttpWebRequest的时候,调用Send方法出现这种错误,这是因为:
因为dotNET的ThreadPool中提供了25个自由线程/CPU(可以在machine.config中修改此数字限制),所以一旦都被占用了,就会报告InvalidOperationException异常,异常提示为:
System.InvalidOperationException: There were not enough free threads in the ThreadPool object to complete the operation |
微软的Stephen Toub是这么说的:
需要知道的第一件事情是,在 Microsoft.NET Framework 1.x 版中,HttpWebRequest 从来不会发出同步请求。我这样说是什么意思呢?让我们来看一看 Shared Source CLI (SSCLI) 中为 HttpWebRequest.GetResponse 编写的代码,此处显示的代码省略了查看以前是否检索了该响应的代码和计算超时的代码:
public override WebResponse GetResponse() {
IAsyncResult asyncResult = BeginGetResponse(null, null);
return EndGetResponse(asyncResult);
}
您可以看出,HttpWebRequest.GetResponse 只是 BeginGetResponse 和 EndGetResponse 对周围的包装。它们是异步运行的,这意味着,BeginGetResponse 从中发出实际 HTTP 请求的线程不同于调用它的线程,而且在该请求完成之前,EndGetResponse 会阻塞。这样做的实际结果是,HttpWebRequest 将每个出站请求的 ThreadPool 排入工作项队列中。因此,我们知道 HttpWebRequest 使用来自 ThreadPool 的线程。
作为该问题的替代解决方案,Framework 小组实现了您正在研究的异常。在 BeginGetResponse 的最后(就在工作项被排入队列之前),使用 System.Net.Connection.IsThreadPoolLow 来查询池中有多少个可用工作线程。如果数量过少(少于 2 个),将会引发 InvalidOperationException。
好消息是,在 .NET Framework 2.0 中,用 HttpWebRequest.GetResponse 发出的同步请求是真正同步的,这样该问题就不复存在了,从而使得您可以将调用 GetResponse 的方法列入您的核心内容。不太好的消息是,您仍然会受到 1.x 版本中这一问题的困扰。一种解决方案是(正如您所指出的),显式限制已经排入队列的工作项的数量或在任何时间内在 ThreadPool 中执行的工作项的数量。要实现这一方法,需要有一种方法来跟踪当前有多少个未完成的工作项,并在达到预定界限之前,阻塞新的请求。
我们可以这么判断当前的自由线程数目:
int wt; int ct; int Count=0; while(true) { if(Count++>20) break;
ThreadPool.GetAvailableThreads(out wt,out ct); if(wt<5) { Thread.Sleep(1000); continue; } else break; } |
除了这个HttpWebRequest问题之外,别的地方也会发生此问题。
在http://support.microsoft.com/default.aspx?scid=kb;en-us;815637
微软演示了以下的程序:
using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Net.Sockets;
namespace ThreadPoolException {
class Class1 { public static void { // Set number of threads to be created for testing. int testThreads = 55; for(int i=0;i<testThreads;i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(PoolFunc)); } Console.ReadLine(); }
static void PoolFunc(object state) { int workerThreads,completionPortThreads; ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads); Console.WriteLine("WorkerThreads: {0}, CompletionPortThreads: {1}", workerThreads, completionPortThreads); Thread.Sleep(10000);
string url ="http://www.msn.com";
HttpWebRequest myHttpWebRequest ; HttpWebResponse myHttpWebResponse=null ; // Creates an HttpWebRequest for the specified URL. myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); // Sends the HttpWebRequest, and waits for a response. myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); myHttpWebResponse.Close(); } } }
|
程序输出如下:
WorkerThreads: 9, CompletionPortThreads: 1000 WorkerThreads: 8, CompletionPortThreads: 1000 WorkerThreads: 7, CompletionPortThreads: 1000 WorkerThreads: 6, CompletionPortThreads: 1000 WorkerThreads: 5, CompletionPortThreads: 1000 WorkerThreads: 4, CompletionPortThreads: 1000 WorkerThreads: 3, CompletionPortThreads: 1000 WorkerThreads: 2, CompletionPortThreads: 1000 WorkerThreads: 1, CompletionPortThreads: 1000
未处理的异常: System.InvalidOperationException: ThreadPool 对象中没有足够的自由 线程来完成操作。 at System.Net.HttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state) at System.Net.HttpWebRequest.GetResponse() at ThreadPoolException.Class1.PoolFunc(Object state) in c:\threadpoolexception\threa dpoolexception\class1.cs:line 41 |
编写者:郑昀@Ultrapower