c# – .NET CLR ThreadPool耗尽 – 实现错误?

我编写了一个简单的基于异步的负载测试库,它还有一个控制台界面,可以从命令行进行测试.

基本上,它同时运行大量请求,聚合它们,并显示摘要和简单的直方图.没有什么花哨.但是我在本地系统中运行了很多测试,因此我想确保测试工具尽可能地使用尽可能少的资源来获得相对准确的基准测试.因此它使用与开始/结束方法的裸异步来保持最小的开销.

所有完成,完全异步,它的工作,并避开(大多数情况下).但正常会话中的线程数远远超过40个.因此,考虑到本地计算机还在运行正在测试的服务器,对于具有4个硬件线程的计算机来说,资源的浪费非常简洁.

我已经在AsyncContext中运行该程序,它基本上只是一个简单的排队上下文,将所有内容放在同一个线程上.因此,所有异步回发都在主线程上.完善.

现在,我所要做的就是限制ThreadPool的最大线程,并查看它的执行情况.将其限制为实际核心,具有4个工作线程和4个IOCP线程.

Result?

Exception: “There were not enough free threads in the ThreadPool
to complete the operation.”

嗯,这不是一个新问题,并且遍布互联网.但是不是ThreadPool的全部意义,你可以将事情放到池的队列中,并且只要线程可用就会执行它?

事实上,该方法名称是“Queue”UserWorkItem.文档适当地说:“将一个执行方法排队.该方法在线程池线程可用时执行.”

现在,如果没有足够的可用线程可用,理想情况下,预期的可能是程序执行速度减慢. IOCP和异步任务应该排在队列中,但为什么它以这样的方式实现,它会被击倒,而失败呢?当它被称为ThreadPool作为队列时,增加线程数不是解决方案.

Edit – Clarification:

I’m fully aware of the concept of the threadpool,and why the CLR
spins up more threads. It should. I agree that it is infact the correct thing to do when there are heavy IO-bound tasks. But the point is,if you do infact restrict the
threads in the ThreadPool,it is expected to queue the task for
execution whenever a free thread is available,not throw an exception.
The concurrency Could be affected,perhaps even slowing down the
outcome,but a QueueWorkUserItem is intented to Queue,not work only
when a new thread is available,or fail – hence,my speculative assertion that its an implementation bug,as stated in the title.

更新1:

与Microsoft支持论坛中记录的问题相同的一个示例:
http://support.microsoft.com/default.aspx?scid=kb;EN-US;815637

解决方法建议,显然是增加线程数,因为它无法排队.

注意:这是在一个非常古老的运行时,并且下面给出了在4.5.1运行时重现相同问题的方法.

更新2:

在Mono Runtime上运行相同的代码片段,ThreadPool似乎没有任何问题.它排队等待并执行.该问题仅在Microsoft CLR下发生.

更新3:

在@ Noseratio指出无法在.NET 4.5.1下重现相同代码的有效问题之后,下面是一段代码将重现该问题.为了打破在按预期排队时工作的代码,所有必须要做的就是向排队的委托添加一个真正的异步调用.

例如,只需将以下行添加到委托的末尾,就应该以异常结束:

(await WebRequest.Create("http://www.google.com").GetResponseAsync()).Close();

复制代码

这是从MSKB文章稍微修改过的代码,在Windows 8.1中的.NET 4.5.1下会很快失败.

(随意更改网址和线程限制).

public static void Main()
{
    ThreadPool.SetMinThreads(1,1);
    ThreadPool.SetMaxThreads(2,2);

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("Queued {0}",i);
        ThreadPool.QueueUserWorkItem(PoolFunc);
    }
    Console.ReadLine();
}

private static async void PoolFunc(object state)
{
    int workerThreads,completionPortThreads;
    ThreadPool.GetAvailableThreads(out workerThreads,out completionPortThreads);
    Console.WriteLine(
        "Available: WorkerThreads: {0},CompletionPortThreads: {1}",workerThreads,completionPortThreads);
    Thread.Sleep(1000);

    string url = "http://localhost:8080";

    HttpWebRequest myHttpWebRequest;
    // Creates an HttpWebRequest for the specified URL.    
    myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
    // Sends the HttpWebRequest,and waits for a response.
    Console.WriteLine("Wait for response.");
    var myHttpWebResponse = await myHttpWebRequest.GetResponseAsync();
    Console.WriteLine("Done.");
    myHttpWebResponse.Close();
}

对此行为的任何洞察都可以为此提供推理,我们非常感激.谢谢.

解决方法

在您的示例代码中,不是对QueueUserWorkItem的调用抛出异常,而是调用等待myHttpWebRequest.GetResponseAsync()抛出异常.如果查看异常详细信息,您可以确切地看到抛出此异常的方法
system.invalidOperationException was unhandled by user code
  _HResult=-2146233079
  _message=There were not enough free threads in the ThreadPool to complete the operation.
  HResult=-2146233079
  IsTransient=false
  Message=There were not enough free threads in the ThreadPool to complete the operation.
  Source=System
  StackTrace:
       at System.Net.HttpWebRequest.BeginGetResponse(AsyncCallback callback,Object state)
       at System.Threading.Tasks.TaskFactory`1.FromAsyncImpl(Func`3 beginMethod,Func`2 endFunction,Action`1 endAction,Object state,TaskCreationoptions creationoptions)
       at System.Threading.Tasks.TaskFactory`1.FromAsync(Func`3 beginMethod,Func`2 endMethod,Object state)
       at System.Net.WebRequest.<GetResponseAsync>b__8()
       at System.Threading.Tasks.Task`1.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
    --- End of stack trace from prevIoUs location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
       at ConsoleApplication1.Program.<PoolFunc>d__0.MoveNext() in c:\Users\Justin\Source\Repos\Azure\ConsoleApplication1\ConsoleApplication1\Program.cs:line 39
  InnerException:

实际上,如果我们看一下HttpWebRequest.BeginGetResponse method,我们可以看到以下内容

if (!RequestSubmitted && NclUtilities.IsThreadPoolLow())
{
    // prevent new requests when low on resources
    Exception exception = new InvalidOperationException(SR.GetString(SR.net_needmorethreads));
    Abort(exception,AbortState.Public);
    throw exception;
}

这个故事的寓意是线程池是一个共享资源,其他代码(包括.Net框架的一部分)也使用 – 将最大线程数设置为2是Raymond Chen称之为本地问题的全局解决方案结果打破了系统其他部分的期望.

如果你想显式控制正在使用的线程,那么你应该创建自己的实现,但是除非你真的知道你在做什么,否则最好让.Net框架处理线程管理.

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...