首先,为什么要在任务中运行线程?在99.9%的情况下,这根本没有意义。在剩下的0.1%情况下,也许有一点点意义,但你最好使用TaskCompletionSource而不是Task。
任务的设计是为了让你可以有调度器来排队这些任务,监视这些任务的休眠/等待等状态,并在此期间重用线程来运行其他任务。
基本上,你将你的“工作”包装成任务,然后将这些任务交给调度器,然后调度器决定是否、何时以及运行多少个线程来执行这些任务。
调度器并不是魔法,它们也没有水晶球来预测未来。我说它们“决定”,但这只是一半的真相:调度器通常遵循某些一般规则,具体取决于它的类型。因此,你选择适合你想象力的正确调度器就可以了。
认真地,放弃当前的方法。使用调度器代替。你甚至可以有一个调度器,它将在单独的线程上执行每个任务。这将等同于你目前的方法。但是,你将能够快速切换到另一个调度器并感受到差异。
以下是几个资源,非常重要的库:
如果你不想阅读等等,那么至少阅读第一篇文章并仅阅读不同调度程序的名称,以了解您选择忽略的可能性数量。
最后,回答问题,是的,Windows有一定的负载均衡。它会尝试防止运行过多的线程。实际上,在给定时刻它会运行少量的线程(大致等于处理器中逻辑执行单元的数量),其余的线程会进入睡眠状态等待执行。Windows会不时地在它们之间切换,所以你会觉得好像它们都在运行,但有些线程速度较慢,有些则运行得更快。
然而,这并不意味着你可以创建无限量的线程。显而易见,存在内存限制:如果你有X GB的内存,那么你不能保留比内存容量还多的线程。我现在有点开玩笑了,但因为有明显的极限,所以肯定还会有其他限制。然而,这里有一点严重性,因为每个线程都有一个堆栈,这个堆栈可能有数百万字节,所以如果你的处理器是32位的话,堆栈的数量最多只能达到几千个。所以.. 是的,内存可能是一个限制。在64位系统上它不太明显,但当然你也没有足够的RAM来填满整个64位地址空间,所以在64位系统上也会有限制。
由于Windows会尝试记录所有线程,即使是那些睡眠的线程,它会浪费时间来跟踪这些记录。此外,它会浪费时间在切换上,因为作为操作系统,它会尝试让它们全部切换和运行。这直接意味着创建的线程越多(1/10/100/1000/..),一切都会变得更慢 - 比仅分成N个线程(不是:1/0.1/0.01/0.001/..,而是:1/0.1/0.097/0.0089/..)更慢,因为时间浪费在记录和切换上。
线程也有优先级。内部系统线程通常具有较高的优先级。系统将更频繁地切换到它们而不是你的线程,这意味着你运行的线程越多,你的应用程序处理速度就会越慢。
还有一个硬限制。为了跟踪重要对象,Windows使用“句柄”的概念。每个窗口、每个线程、每个共享内存块、每个打开的文件流等,只要它还活着(并且再长一点)- 就有一个唯一的句柄。你实际上可以通过使用所有句柄来使Windows“饥饿”。
例如,如果您使用完所有GUI句柄,您将无法打开新窗口。或窗口区域。或控件。想象一下打开一个记事本,它启动并显示没有菜单和没有文本区域,因为没有足够的空闲句柄来分配给它们。
由于该限制,Windows实际上限制了每个进程分配的句柄数量。这意味着,比如说,Windows有一个1M句柄池,但每个进程只能使用最多1K。这些数字是人为设定的,只是为了让您有一个概念。
由于物理(本地)线程必须具有句柄,这里还有另一个限制。
我对这个问题不是真正的专家,所以让我们回到一系列专家撰写的文章,他们涵盖了线程限制、句柄限制等等:
https://blogs.technet.microsoft.com/markrussinovich/2009/07/05/pushing-the-limits-of-windows-processes-and-threads/