我想创建一个程序来抓取和检查我的网站是否存在 http 错误和其他内容。我想使用多个线程来执行此操作,这些线程应该接受要抓取的 url 等参数。虽然我希望 X 线程处于活动状态,但仍有 Y 任务等待执行。
现在我想知道执行此操作的最佳策略是什么:ThreadPool、Tasks、Threads 还是其他什么?
我想创建一个程序来抓取和检查我的网站是否存在 http 错误和其他内容。我想使用多个线程来执行此操作,这些线程应该接受要抓取的 url 等参数。虽然我希望 X 线程处于活动状态,但仍有 Y 任务等待执行。
现在我想知道执行此操作的最佳策略是什么:ThreadPool、Tasks、Threads 还是其他什么?
这是一个示例,展示了如何将一堆任务排队但限制同时运行的数量。它使用 aQueue
来跟踪准备运行的任务,并使用 aDictionary
来跟踪正在运行的任务。当任务完成时,它会调用一个回调方法将自己从Dictionary
. 一种async
方法用于在空间可用时启动排队的任务。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MinimalTaskDemo
{
class Program
{
private static readonly Queue<Task> WaitingTasks = new Queue<Task>();
private static readonly Dictionary<int, Task> RunningTasks = new Dictionary<int, Task>();
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);
}
}
}
我建议使用 (asynchronous)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<string> ProcessHtml(string html)
{
// return all urls in the html string.
}
你可以用一个简单的开始:
await Crawl("http://example.org/");
我建议使用 threadPool。Is 很容易使用,因为它有几个好处:
“线程池将通过重用已经创建的线程而不是创建新线程(一个昂贵的过程)来为频繁和相对较短的操作提供好处。当对新工作项的请求激增时限制线程创建速率(我相信这仅在 .NET 3.5 中)
如果您将 100 个线程池任务排队,它只会使用已创建的线程数来服务这些请求(例如 10 个)。线程池会进行频繁的检查(我相信在 3.5 SP1 中每 500 毫秒),如果有排队的任务,它会创建一个新线程。如果您的任务很快,那么新线程的数量将很少,并且为短任务重用 10 个左右的线程将比预先创建 100 个线程更快。
如果您的工作负载始终有大量线程池请求进入,那么线程池将通过上述过程在池中创建更多线程来调整自己以适应您的工作负载,以便有更多线程可用于处理请求“
好吧,Task
这是一个好方法,因为这意味着您不必担心编写大量“管道”代码。
我建议您也查看 Joe Albahari 的关于线程的网站,它是关于线程的非常好的入门书: