针对多个Web请求的最佳multithreading方法

我想创建一个程序来抓取并检查我的网站是否存在http错误和其他内容。 我想用多个线程来做这件事,这些线程应该接受像抓取的url这样的参数。 虽然我希望X线程处于活动状态,但是已经有Y任务等待执行。

现在我想知道执行此操作的最佳策略是什么:ThreadPool,Tasks,Threads甚至是其他什么?

这是一个示例,说明如何排队一堆任务但限制同时运行的数量。 它使用Queue来跟踪准备运行的任务,并使用Dictionary来跟踪正在运行的任务。 当任务完成时,它会调用一个回调方法将自己从Dictionary删除。 async方法用于在空间可用时启动排队任务。

 using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MinimalTaskDemo { class Program { private static readonly Queue WaitingTasks = new Queue(); private static readonly Dictionary RunningTasks = new Dictionary(); public static int MaxRunningTasks = 100; // vary this to dynamically throttle launching new tasks static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; Worker.Done = new Worker.DoneDelegate(WorkerDone); for (int i = 0; i < 1000; i++) // queue some tasks { // task state (i) will be our key for RunningTasks WaitingTasks.Enqueue(new Task(id => new Worker().DoWork((int)id, token), i, token)); } LaunchTasks(); Console.ReadKey(); if (RunningTasks.Count > 0) { lock (WaitingTasks) WaitingTasks.Clear(); tokenSource.Cancel(); Console.ReadKey(); } } static async void LaunchTasks() { // keep checking until we're done while ((WaitingTasks.Count > 0) || (RunningTasks.Count > 0)) { // launch tasks when there's room while ((WaitingTasks.Count > 0) && (RunningTasks.Count < MaxRunningTasks)) { Task task = WaitingTasks.Dequeue(); lock (RunningTasks) RunningTasks.Add((int)task.AsyncState, task); task.Start(); } UpdateConsole(); await Task.Delay(300); // wait before checking again } UpdateConsole(); // all done } static void UpdateConsole() { Console.Write(string.Format("\rwaiting: {0,3:##0} running: {1,3:##0} ", WaitingTasks.Count, RunningTasks.Count)); } // callback from finished worker static void WorkerDone(int id) { lock (RunningTasks) RunningTasks.Remove(id); } } internal class Worker { public delegate void DoneDelegate(int taskId); public static DoneDelegate Done { private get; set; } private static readonly Random Rnd = new Random(); public async void DoWork(object id, CancellationToken token) { for (int i = 0; i < Rnd.Next(20); i++) { if (token.IsCancellationRequested) break; await Task.Delay(100); // simulate work } Done((int)id); } } } 

我建议使用(异步) Task来下载数据然后处理(在线程池上)。

我建议您限制每个目标服务器的请求数量,而不是限制任务。 好消息:.NET 已经为您做到了这一点 。

这使您的代码简单如下:

 private static readonly HttpClient client = new HttpClient(); public async Task Crawl(string url) { var html = await client.GetString(url); var nextUrls = await Task.Run(ProcessHtml(html)); var nextTasks = nextUrls.Select(nextUrl => Crawl(nextUrl)); await Task.WhenAll(nextTasks); } private IEnumerable ProcessHtml(string html) { // return all urls in the html string. } 

您可以通过简单的方式开始:

 await Crawl("http://example.org/"); 

我建议使用threadPool。 很容易使用,因为它有一些好处:

“通过重用已经创建的线程而不是创建新线程(一个昂贵的进程),线程池将为频繁和相对较短的操作带来好处。当有新的工作项请求突发时,限制线程创建的速度(我相信这仅适用于.NET 3.5)

如果排队100个线程池任务,它将只使用已经创建的线程来为这些请求提供服务(例如10个)。 线程池将进行频繁检查(我相信3.5 SP1中的每500ms),如果有排队任务,它将创建一个新线程。 如果您的任务很快,那么新线程的数量将会很少,并且重复使用10个左右的线程来完成短任务将比预先创建100个线程更快。

如果您的工作负载始终存在大量线程池请求,则线程池将通过上述过程在池中创建更multithreading来调整自身以适应您的工作负载,以便有更多可用于处理请求的线程“

线程与ThreadPool

好吧, Task是一个很好的方法,因为这意味着你不必担心编写很多“管道”代码。

我建议您查看Joe Albahari的线程网站,这是一个很好的线程入门:

http://www.albahari.com/threading/