C# .NET CLR线程池耗尽-实现错误?

C# .NET CLR线程池耗尽-实现错误?,c#,.net,mono,clr,async-await,C#,.net,Mono,Clr,Async Await,我编写了一个简单的基于异步的负载测试库,它还有一个控制台接口,可以从命令行进行测试 基本上,它同时运行大量请求,对它们进行聚合,并显示摘要和简单的直方图。没什么特别的。但是我在本地系统中运行了很多测试,所以我想确保测试工具能够使用尽可能少的资源,获得相对准确的基准测试。因此,它使用带有Begin/End方法的裸异步来保持最小的开销 所有这些都完成了,完全异步,它可以正常工作,并且不会碍事(嗯,大部分是这样)。但正常会话中的线程数远远超过40。因此,考虑到本地机器也在运行正在测试的服务器,对于一台

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

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

所有这些都完成了,完全异步,它可以正常工作,并且不会碍事(嗯,大部分是这样)。但正常会话中的线程数远远超过40。因此,考虑到本地机器也在运行正在测试的服务器,对于一台有4个硬件线程的机器来说,这是一种非常好的资源浪费

我已经在AsyncContext中运行了这个程序,它基本上只是一个简单的排队上下文,将所有内容放在同一个线程上。因此,所有的aync后处理都在主线程上。太好了

现在,我要做的就是限制线程池的最大线程数,看看它的性能如何。将其限制为实际内核,具有4个工作线程和4个IOCP线程

结果?

异常:“线程池中没有足够的可用线程 以完成该操作。”

嗯,这不是一个新问题,在互联网上非常分散。但是线程池的全部意义不就是,你可以把东西放到线程池的队列中,只要线程可用,它就会执行吗

实际上,该方法的名称是“队列”“UserWorkItem”。文档中恰当地说,“将方法排队执行。当线程池线程可用时,该方法执行。”

现在,如果没有足够的可用线程,理想情况下,预期的结果可能是程序执行速度减慢。IOCP和异步任务应该只是排队,但为什么它的实现方式会导致失败呢?当线程池被称为队列时,增加线程数量不是解决方案

编辑-澄清:

我完全了解线程池的概念,以及为什么要使用CLR 旋转更多的线。应该这样。我同意,当有繁重的IO任务时,这实际上是正确的做法。但关键是,如果你真的限制 线程池中的线程,它将为 每当空闲线程可用时执行,不抛出异常。 并发性可能会受到影响,甚至可能会减慢运行速度 结果,但QueueWorkUserItem旨在排队,而不仅仅是工作 当一个新线程可用或失败时——因此,我推测这是一个实现错误,如标题中所述

更新1:

与Microsoft支持论坛中记录的问题相同,例如:

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

注意:这是在非常旧的运行时下进行的,下面给出了在4.5.1运行时上重现相同问题的方法

更新2:

在Mono运行时运行相同的代码,线程池似乎没有问题。它排队并执行。该问题仅在Microsoft CLR下发生

更新3:

@nosertio指出了无法在.NET4.5.1下复制相同代码的有效问题后,下面是一段将复制该问题的代码。为了打破在按预期排队时工作的代码,真正需要做的就是向排队委托添加一个真正的异步调用

例如,仅将下面的一行添加到代理的末尾应该会导致异常:

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

这是一段从MSKB文章中稍加修改的代码,在Windows8.1的.NET4.5.1下应该会很快失败

(请随意更改url和线程限制)

publicstaticvoidmain()
{
SetMinThreads(1,1);
SetMaxThreads(2,2);
对于(int i=0;i<5;i++)
{
WriteLine(“排队的{0}”,i);
ThreadPool.QueueUserWorkItem(PoolFunc);
}
Console.ReadLine();
}
私有静态异步void PoolFunc(对象状态)
{
int workerThreads,completionPortThreads;
GetAvailableThreads(out workerThreads,out completionPortThreads);
控制台写入线(
“可用:WorkerThreads:{0},CompletionPortThreads:{1}”,
工作线程,
completionPortThreads);
睡眠(1000);
字符串url=”http://localhost:8080";
HttpWebRequest myHttpWebRequest;
//为指定的URL创建HttpWebRequest。
myHttpWebRequest=(HttpWebRequest)WebRequest.Create(url);
//发送HttpWebRequest,并等待响应。
控制台。WriteLine(“等待响应”);
var myHttpWebResponse=等待myHttpWebRequest.GetResponseAsync();
控制台。WriteLine(“完成”);
myHttpWebResponse.Close();
}

任何对这一行为的洞察,都会给这一行为带来推理,这是非常值得赞赏的。谢谢。

在示例代码中,引发异常的不是对
QueueUserWorkItem
的调用,而是对
Wait 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: 
这个故事的寓意是线程池是其他代码(包括.Net framework的部分)也使用的共享资源-将最大线程数设置为2是Raymond Chen所说的局部问题的全局解决方案,因此是brea
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: 
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;
}