ウェブサイトをクロールして http エラーなどをチェックするプログラムを作成したいと考えています。クロールするURLなどのパラメーターを受け入れる必要がある複数のスレッドでこれを行いたいです。X 個のスレッドをアクティブにしたいのですが、Y 個のタスクが実行待ちになっています。
これを行うための最良の戦略は何かを知りたいと思いました:ThreadPool、Tasks、Threads、または他の何か?
ウェブサイトをクロールして 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<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);
}
}
}
(非同期) を使用し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 を使用することをお勧めします。いくつかの利点があるため、簡単に使用できます。
「スレッドプールは、新しいスレッドを作成する代わりに、すでに作成されているスレッドを再利用することにより、頻繁で比較的短い操作に利点を提供します (コストのかかるプロセス)。これは .NET 3.5 のみです)
100 個のスレッド プール タスクをキューに入れると、これらの要求を処理するために既に作成されている数 (たとえば 10 個) のスレッドのみが使用されます。スレッド プールは頻繁にチェックを行い (3.5 SP1 では 500 ミリ秒ごとだと思います)、キューに入れられたタスクがある場合は、新しいスレッドを 1 つ作成します。タスクが高速な場合、新しいスレッドの数は少なくなり、短いタスクに 10 程度のスレッドを再利用する方が、事前に 100 のスレッドを作成するよりも高速になります。
ワークロードに大量のスレッド プール リクエストが常に入ってくる場合、スレッド プールは、上記のプロセスによってプール内により多くのスレッドを作成することでワークロードに合わせて調整し、リクエストの処理に使用できるスレッド数を増やします。」
これTask
は、多くの「配管」コードを書くことを心配する必要がないことを意味するため、良い方法です。
スレッドに関する Joe Albahari の Web サイトもチェックすることをお勧めします。これは、スレッドに関する非常に優れた入門書です。