`
qiujiayu
  • 浏览: 173536 次
  • 性别: 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 属性可为所有主机增加此默认值。”
分享到:
评论

相关推荐

    C#判断线程池中所有的线程是否已经完成

    在`CheckThreadPool`方法中,我们通过`ThreadPool.GetAvailableThreads`获取当前可用的工作者线程(workerThreads)和完成端口线程(completionPortThreads)数量。`ThreadPool.GetMaxThreads`则用于获取线程池的...

    易语言线程互斥对象解决

    从提供的文件名"ThreadMutex.cpp"和"ThreadMutex.e"来看,这些文件可能包含了易语言中关于线程互斥的实现示例。在实际项目中,你可以参考这些代码了解如何在易语言中创建、使用和管理Mutex,以及如何在多线程环境中...

    C# Thread、ThreadPool、Task测试

    在C#编程中,线程(Thread)、线程池(ThreadPool)和任务(Task)是并行处理和异步操作的重要组成部分。理解它们的工作原理和使用方法对于优化应用程序的性能至关重要。下面将详细阐述这三个概念及其相关知识点。 ...

    Thread线程和ThreadPool线程池 Thread:我们可以开启一个线程

    Thread线程和ThreadPool线程池 Thread:我们可以开启一个线程。但是请大家记住:线程开启会在空间和时间上有不小的开销。所以,不能随便开。 ThreadPool:会根据你的CPU的核心数开启一个最合适的线程数量。如果你...

    C#winform程序Thread(线程)和ThreadPool(线程池)的基本用法

    在C#编程中,线程(Thread)和线程池(ThreadPool)是处理并发操作的重要概念,尤其是在开发Windows Forms(WinForm)应用程序时。本文将详细介绍C#中Thread和ThreadPool的基本用法及其应用场景。 ## 1. 线程...

    Linux_thread_pool.zip_linux_linux threadpool.cpp_linux 多线程_linux

    在Linux操作系统中,多线程是一种并发编程技术,它允许程序同时执行多个任务。线程池是这种技术的一种优化方式,它可以有效地管理线程资源,提高系统的效率和响应性。`Linux_thread_pool.zip`中的`linux_threadpool....

    C#线程进程操作

    其中,`MyMethod`是需要在线程中执行的方法。 **线程同步**是防止多个线程同时访问同一资源造成数据不一致的重要手段。C#提供了多种同步机制,如`Mutex`、`Semaphore`、`Monitor`(锁)以及`lock`关键字。例如,...

    C#多线程操作技术

    在.NET程序设计中,线程是使用Thread类(或Timer类(线程计数器)、ThreadPool类(线程池))来处理的,这些类在System.Threading命名空间中: using System.Threading; Thread类:(实现线程的主要方法)一个...

    C++11 线程池 ThreadPool

    在C++11中,我们可以使用`std::thread`类来创建和管理线程。线程池管理器还需要一个任务队列来存储待执行的任务。 2. **任务队列**:线程池中的任务不是直接提交给某一线程执行的,而是先放入一个任务队列。当线程...

    vb 多线程 实例(thread 类创建)

    本实例将详细讲解如何利用Thread类来创建和管理线程。 首先,理解线程的基本概念至关重要。线程是程序执行的最小单元,每个线程都拥有自己的内存空间,可以独立执行代码。在VB中,多线程通常用于执行耗时操作,如...

    C#多线程解决界面卡死问题的完美解决方案

    在C#编程中,多线程技术是一种关键的性能优化手段,尤其对于处理耗时操作时,能够确保用户界面(UI)的响应性,避免出现界面卡死的现象。本解决方案将深入探讨如何利用C#的多线程特性来解决这个问题。 一、线程基础...

    C#Winform异步多线程和线程池集成的用法

    在C#中,可以使用`System.Threading.Thread`类来创建和管理线程。通过创建新的线程实例并调用其`Start()`方法,我们可以在后台执行耗时任务,避免阻塞主线程(UI线程),从而保持用户界面的流畅性。例如: ```...

    C#多线程学习 Thread类使用 线程等编程方法

    System.Threading命名空间中的Thread类是实现多线程的基础,提供了创建、控制和管理线程的方法。本文将深入探讨C#多线程编程,包括Thread类的使用以及线程同步的相关知识。 ### 1. Thread类介绍 Thread类是C#中处理...

    通过Thread建立线程

    `Thread`类是.NET框架中的核心组件,用于创建和管理线程。本篇文章将详细探讨如何通过`Thread`类在C#中建立线程,并深入理解线程的工作原理及相关知识点。 首先,创建一个新的线程通常涉及到以下几个步骤: 1. **...

    ThreadPool

    标题中的"ThreadPool"指的是线程池,这是一个编程概念,特别是在多线程编程中非常关键。线程池是一种线程使用模式,它维护着一个工作线程的集合,用于执行一系列的任务。通过线程池,可以有效地管理和控制并发执行的...

    ThreadPool UML图

    - ThreadPool:表示整个线程池对象,它负责管理所有线程和任务队列。 - TaskQueue:任务队列类,负责存放待执行的任务。 - WorkerThread:工作线程类,表示线程池中的单个线程,它负责从任务队列中取出任务并执行。 ...

    线程创建和管理

    例如,在一个图形用户界面程序中,我们可以使用Thread类来创建一个独立的线程,该线程可以在后台执行一些耗时的操作,而不影响主线程的执行。 三、线程管理 线程管理是多线程编程中一个非常重要的概念,合理地管理...

    C++ 等待线程结束

    创建一个线程通常通过`std::thread`类来完成。例如: ```cpp #include #include &lt;thread&gt; void workerFunction() { // 线程执行的代码 std::cout &lt;&lt; "Worker thread executing" ; } int main() { std::thread...

    PyQt5多线程的执行和停止

    在PyQt5中,通常使用`QMutex`或`QSemaphore`等同步对象来解决这些问题。 总的来说,PyQt5的多线程编程结合进度条的使用,能够帮助开发者构建高效、用户友好的应用程序。理解并熟练掌握这些技术,对于编写高质量的...

    VB多线程操作

    在VB(Visual Basic)编程环境中,多线程操作是一个关键的概念,它允许程序同时执行多个独立的任务,从而提高应用程序的效率和响应性。在标题"VB多线程操作"中,我们聚焦的是如何在VB中实现和管理多线程。 多线程在...

Global site tag (gtag.js) - Google Analytics