异步任务’堵塞’

最近我开始尝试大规模搜索一个网站用于存档目的,我认为让多个Web请求异步工作以加快速度是个好主意(10,000,000页对于存档来说肯定很多)所以我冒险进入三分钟之后,我开始想知道为什么我正在创建的任务(通过Task.Factory.StartNew )是“堵塞”。

懊恼和好奇我决定测试一下,看它是不是仅仅是因为环境的结果,所以我在VS2012中创建了一个新的控制台项目并创建了这个:

 static void Main(string[] args) { for (int i = 0; i  { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); Thread.Sleep(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); }); } Console.ReadKey(); } 

运行时出现了这个结果:

检测结果

正如您所看到的,前四个任务在快速连续时间内以~0.27的时间内开始,然而在此之后,任务开始时的任务开始急剧增加。

为什么会发生这种情况,我该怎么做才能解决或解决这个限制?

任务(默认情况下)在线程池上运行,就像它听起来一样,是一个线程池。 线程池针对很多情况进行了优化,但是在那里抛出Thread.Sleep可能会在大多数情况下抛出一个扳手。 此外, Task.Factory.StartNew通常是一个坏主意,因为人们不理解它是如何工作的。 试试这个:

 static void Main(string[] args) { for (int i = 0; i < 10; i++) { int i2 = i + 1; Stopwatch t = new Stopwatch(); t.Start(); Task.Run(async () => { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); await Task.Delay(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); }); } Console.ReadKey(); } 

更多解释:

线程池可以使用有限数量的线程。 该数字根据某些条件而变化,但是,一般情况下也是如此。 出于这个原因,你永远不应该在线程池上做任何阻塞(如果你想实现并行性)。 Thread.Sleep是阻塞API的完美示例,但大多数Web请求API也是如此,除非您使用较新的异步版本。

因此,原始程序中的爬网问题可能与您发布的示例中的问题相同。 您正在阻止所有线程池线程,因此它被迫启动新线程,并最终阻塞。

额外的好东西

巧合的是,以这种方式使用Task.Run还可以轻松地以这样的方式重写代码,以便您可以知道它何时完成。 通过存储对所有已启动任务的引用,并在最后等待它们(这不会阻止并行性),您可以可靠地知道所有任务何时完成。 以下显示了如何实现:

 static void Main(string[] args) { var tasks = new List(); for (int i = 0; i < 10; i++) { int i2 = i + 1; Stopwatch t = new Stopwatch(); t.Start(); tasks.Add(Task.Run(async () => { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); await Task.Delay(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine("All tasks completed"); Console.ReadKey(); } 

注意:此代码尚未经过测试

阅读更多

关于Task.Factory.StartNew更多信息以及为什么要避免它: http : //blog.stephencleary.com/2013/08/startnew-is-dangerous.html 。

我认为这是因为您已经耗尽了线程池中的所有可用线程。 尝试使用TaskCreationOptions.LongRunning启动任务。 更多细节在这里 。

另一个问题是你正在使用Thread.Sleep ,这会阻塞当前线程并浪费资源。 尝试使用await Task.Delay异步await Task.Delay 。 您可能需要将lambda更改为async

 Task.Factory.StartNew(async () => { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); await Task.Delay(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); });