我编写了一个简单的基于异步的负载测试库,它还有一个控制台界面可以从命令行进行测试。
基本上,它同时运行大量请求,聚合它们,并显示摘要和简单的直方图。没有什么花哨。但我在本地系统中运行了大量测试,因此我想确保测试工具能够使用尽可能少的资源来获得相对准确的基准测试。因此它使用带有 Begin/End 方法的裸异步来保持最小的开销。
一切都完成了,完全异步,它可以工作,并且不会妨碍(好吧,大部分)。但正常会话中的线程数量远远超过 40 个。因此,考虑到本地计算机也运行正在测试的服务器,对于具有 4 个硬件线程的计算机来说,这确实是一种资源浪费。
我已经在 AsyncContext 中运行该程序,它基本上只是一个简单的排队上下文,将所有内容都放在同一个线程上。因此,所有 aync 回发都在主线程上。完美的。
现在,我所要做的就是限制 ThreadPool 的最大线程数,并看看它的性能如何。将其限制为实际核心,具有 4 个工作线程和 4 个 IOCP 线程。
Result?
异常:“线程池中没有足够的空闲线程
来完成操作。”
嗯,这不是一个新问题,并且在互联网上非常分散。但是,ThreadPool 的全部意义不就是您可以将事物放入池的队列中,并在线程可用时执行它吗?
事实上,该方法的名称是'Queue' 用户工作项。文件恰当地说:“将方法排队等待执行。当线程池线程可用时执行该方法."
现在,如果没有足够的可用线程,理想情况下,预期的情况可能是程序执行速度减慢。 IOCP、异步任务本来就应该排队,但是为什么会以这样的方式实现呢,反而会撞倒,失败呢?当线程池被称为队列时,增加线程数量并不是解决方案。
编辑-澄清:
我完全了解线程池的概念,以及为什么 CLR
启动更多线程。它应该。我同意,当存在繁重的 IO 密集型任务时,这实际上是正确的做法。但关键是,如果你确实限制
线程池中的线程,预计将任务排队
每当有空闲线程可用时执行,不抛出异常。
并发可能会受到影响,甚至可能减慢
结果,但 QueueWorkUserItem 旨在队列,而不仅仅是工作
当新线程可用或失败时 - 因此,我推测它是一个实现错误,如标题中所述。
更新1:
与 Microsoft 支持论坛中记录的相同问题并附有示例:http://support.microsoft.com/default.aspx?scid=kb;EN-US;815637 http://support.microsoft.com/default.aspx?scid=kb;EN-US;815637
建议的解决方法显然是增加线程数量,因为它无法排队。
注意:这是在非常旧的运行时下进行的,下面给出了在 4.5.1 运行时重现相同问题的方法。
更新2:
Ran 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 下应该会很快失败。
(随意更改 url 和线程限制)。
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();
}
任何对这种行为的洞察,可以对此进行推理,我们都非常感激。谢谢。