`
qiujiayu
  • 浏览: 170776 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

[Thread]“ThreadPool 对象中没有足够的自由线程来完成操作”的现象和解决办法

    博客分类:
  • C#
阅读更多

[Thread]“ThreadPool 对象中没有足够的自由线程来完成操作”的现象和解决办法

 

其实微软有一篇《异步 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 Main()

            {

                // 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

 

 

 

Feedback

#1楼  回复  引用    

2005-12-05 21:12 by bnflower[未注册用户]
为什么要关闭myHttpWebResponse.Close()?

我试了这段代码,连接到内网的服务器。发现如果不myHttpWebResponse.Close();只能连接上2个线程,其他的都阻塞知道超时,这是为什么呢?

我不Close是因为随后我需要这个连接来下载服务器端的文件。

#2楼 [楼主 ]  回复  引用  查看    

2005-12-06 10:19 by 让变化成为计划的一部分       
建议好好阅读http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemnethttpwebresponseclassclosetopic.asp ,看看close方法到底是为什么要调用的。
另外,建议你深入了解windows客户端web request长久以来的一个潜规则,不管是dotnet的,还是以前的msxml.XMLHttpRequest,都遵循这样的一个规则,客户端发起http请求最多并发2个/4个,其他的将阻塞。
你可以看看我的这篇文档:
http://blog.csdn.net/zhengyun_ustc/archive/2002/05/20/12652.aspx
上面明确列出:
Because XMLHTTP is designed for single-user client applications and is based on WinInet, it enforces strict limits on the number of simultaneous connections to a given server.

The limit is either 2 or 4 (depending on HTTP version).
之后的其他MS客户端WebRequest应用也都遵循这个设计规则。

#3楼 [楼主 ]  回复  引用  查看    

2005-12-06 10:26 by 让变化成为计划的一部分       
更进一步的解释是:
通常,人们忘记了request创建的underlying connection并没有被释放,直到你调用response的close()方法。
这样,你将hit the connection limit,而且不再有web request能够进行。
让我们看下面的例子:

for(int i=0; i < 3; i++) {
HttpWebRequest r = WebRequest.Create(“http://www.microsoft.com “) as HttpWebRequest;
HttpWebResponse w = r.GetResponse() as HttpWebResponse;
}

上面的代码是有问题的,因为第三个request将一直挂起在GetResponse()语句上。

如果你调用Response.Close() 关闭了response,underlying connection才会被释放,第三个request才会成功。

记住,这是一个潜规则,而且这是MS专门设计成这样的。

#4楼 [楼主 ]  回复  引用  查看    

2005-12-06 10:38 by 让变化成为计划的一部分       
Feroze Daud said :
A tale of threads
Today's lesson is about thread interaction between Asp.Net and the HttpWebRequest object of the System.Net namespace.

The CLR provides a threadpool. This facility provides threads for normal work ( worker threads ), I/O work ( I/O threads) and Timer Threads. Normally, the limit of threads for managed processes is set as follows:

WorkerThreads: 25 per CPU

I/O Threads: 1000

Asp.Net uses worker threads to dispatch incoming requests to page handlers (ASPX/ASMX etc). If you write an ASPX page, your request will run on a worker thread.

It turns out that System.Net also uses the threadpool. Worker Threads are used to kick of connect's to servers, and I/O threads are used to service I/O completions for Sockets.

Normally, people create middle tier applications by creating a WebRequest inside of their Page_Load(), and then doing a GetResponse(). Normally, a synchronous function (like GetResponse()) would complete on the same thread on which it was invoked. However, GetResponse() is actually implemented internally as follows:

void GetResponse() {

IAsyncResult ar = BeginGetResponse(null,null);

ar.WaitOne();

return EndGetResponse(ar);

}

It so happens that while you are waiting for the operation to complete, another thread is being spawned iternally to service the request, by kicking off the connect to the server, parsing the response etc.

If you have a page which is under high load, then you can run into potential deadlock issues. The user's code is running on a worker thread. When the user does a GetResponse(), another threadpool worker thread is used internally to kick off the operation. Imagine that there are 25 threadpool worker threads which are executing asp.net code (eg: page handlers etc). One of these threads is executing a page which is trying to do the webrequest.GetResponse().

System.Net will do a ThreadPool.QueueUserWorkItem() to complete the GetResponse(). However, since the CLR threadpool is exhausted ( uniprocessor limit is 25), CLR will not create a new thread to service the request. If no thread becomes available, then the GetResponse() will never complete. At the same time, the worker thread that asp.net is running the user code on, will not complete either. Now multiply this situation across all threads, and you have a classic deadlock.

This problem is mitigated somewhat in the V1 & V1.1 versions of the framework. There, System.Net will query the threadpool for available threads. If it determines that there are no free threads available to service the request, it will throw an exception, so that the users code can handle it.

Note that this problem can also happen in standalone applications.

So, the next time you get an exception from GetResponse() or GetRequestStream(), saying that “There are no available free threads to service this operation“ it is by design :-)

posted on Sunday, February 01, 2004 7:20 PM by Feroze Daud

#5楼  回复  引用    

2005-12-11 12:18 by bnflower[未注册用户]
在MSND上我查到了HttpWebRequest有一个属性ServicePoint。
ServicePoint类有一个成员ConnectionLimit ,通过设置这个值可以改变客户端向所连接的Web服务请求的并发连接数。
request =(HttpWebRequest)WebRequest.Create(strUrl);
request.ServicePoint.ConnectionLimit = 10; //并发连接数为10个

这样
for(int i=0; i < 3; i++) {
HttpWebRequest r = WebRequest.Create(“http://www.microsoft.co “)as HttpWebRequest;
HttpWebResponse w = r.GetResponse() as HttpWebResponse;
}
就不会在第三个r.GetResponse() as HttpWeResponse堵塞了。

看来似乎连接数的发起者是由客户端来决定的(在服务器所允许的最大连接数大于客户端请求连接数的情况下),通过这个可以创建多条与Web服务器的连接数了。

我没有使用过msxml.XMLHttpRequest,不知道是否在msxml.XMLHttpRequest也存在同样的情况。

#6楼 [楼主 ]  回复  引用  查看    

2005-12-11 22:47 by 让变化成为计划的一部分       
你说得对。
http://blog.joycode.com/musicland/archive/2005/04/16/48690.aspx ,有人回复也提到类似的解决方法:“
re: Concurrent connection limit 2005-4-17 0:59 Wang Ting
方法三:

ServicePointManager.DefaultConnectionLimit = 1000;

方法四:

protected override WebRequest GetWebRequest(Uri uri) {
HttpWebRequest req = (HttpWebRequest)base.GetWebRequest(uri);
ServicePoint currentServicePoint = req.ServicePoint;
currentServicePoint .ConnectionLimit = 1000;
return req;
}
”。

#7楼 [楼主 ]  回复  引用  查看    

2005-12-11 22:49 by 让变化成为计划的一部分       
上面提到的方法可以用这样的代码测试:
ServicePointManager.DefaultConnectionLimit = 4;
string url = "http://www.microsoft.com/";
Uri uri = new Uri(url);
ServicePoint spSite = ServicePointManager.FindServicePoint(uri);
spSite.ConnectionLimit = 4;
HttpWebRequest request;
Stream[] ns = new Stream[4];
for(int i=0;i<4;i++)
{
request = (HttpWebRequest)HttpWebRequest.Create(url);
ns[i] = request.GetResponse().GetResponseStream();
}

#8楼 [楼主 ]  回复  引用  查看    

2005-12-11 22:56 by 让变化成为计划的一部分       
你可能要看
http://blogs.msdn.com/feroze_daud/archive/2004/11/22/268277.aspx

“To mitigate this, you want to use a property on the webrequest called UnsafeAuthenticatedConnectionSharing . Setting this property will cause HttpWebRequest to reuse authenticated connections (making sure that it honors ServicePoint.ConnectionLimit).

Security Note: You dont want to use this property lightly. It has security consequences.
”。
另外,
“对 Internet 资源打开的连接数可能对网络性能和吞吐量有显著的影响。默认情况下,System.Net 对每个主机的每个应用程序使用两个连接。设置应用程序的 ServicePoint 中的 ConnectionLimit 属性可为特定主机增加此数目。设置 ServicePointManager.DefaultPersistentConnectionLimit 属性可为所有主机增加此默认值。”
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics