最近,我开始尝试大规模抓取网站以进行存档,我认为让多个 Web 请求异步工作以加快速度是一个好主意(10,000,000 个页面绝对需要存档),因此我冒险进入并行性的严厉女主人,三分钟后我开始想知道为什么我正在创建的任务(通过Task.Factory.StartNew
)正在“堵塞”。
既恼火又好奇,我决定对此进行测试,看看这是否只是环境造成的,所以我在 VS2012 中创建了一个新的控制台项目并创建了以下内容:
static void Main(string[] args)
{
for (int i = 0; i < 10; i++) {
int i2 = i + 1;
Stopwatch t = new Stopwatch();
t.Start();
Task.Factory.StartNew(() => {
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<Task>();
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.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)