4

ウェブサイトをクロールして http エラーなどをチェックするプログラムを作成したいと考えています。クロールするURLなどのパラメーターを受け入れる必要がある複数のスレッドでこれを行いたいです。X 個のスレッドをアクティブにしたいのですが、Y 個のタスクが実行待ちになっています。

これを行うための最良の戦略は何かを知りたいと思いました:ThreadPool、Tasks、Threads、または他の何か?

4

4 に答える 4

7

これは、一連のタスクをキューに入れるが、同時に実行される数を制限する方法を示す例です。を使用しQueueて実行する準備ができているタスクを追跡し、 を使用して実行Dictionary中のタスクを追跡します。タスクが終了すると、コールバック メソッドを呼び出して、 から自身を削除しますDictionaryasyncスペースが利用可能になると、キューに入れられたタスクを起動するメソッドが使用されます。

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);
        }
    }
}
于 2013-03-30T02:33:18.133 に答える
4

(非同期) を使用し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/");
于 2013-03-30T03:01:11.280 に答える
0

threadPool を使用することをお勧めします。いくつかの利点があるため、簡単に使用できます。

「スレッドプールは、新しいスレッドを作成する代わりに、すでに作成されているスレッドを再利用することにより、頻繁で比較的短い操作に利点を提供します (コストのかかるプロセス)。これは .NET 3.5 のみです)

100 個のスレッド プール タスクをキューに入れると、これらの要求を処理するために既に作成されている数 (たとえば 10 個) のスレッドのみが使用されます。スレッド プールは頻繁にチェックを行い (3.5 SP1 では 500 ミリ秒ごとだと思います)、キューに入れられたタスクがある場合は、新しいスレッドを 1 つ作成します。タスクが高速な場合、新しいスレッドの数は少なくなり、短いタスクに 10 程度のスレッドを再利用する方が、事前に 100 のスレッドを作成するよりも高速になります。

ワークロードに大量のスレッド プール リクエストが常に入ってくる場合、スレッド プールは、上記のプロセスによってプール内により多くのスレッドを作成することでワークロードに合わせて調整し、リクエストの処理に使用できるスレッド数を増やします。」

スレッドとスレッドプール

于 2013-03-29T15:40:31.307 に答える
-1

これTaskは、多くの「配管」コードを書くことを心配する必要がないことを意味するため、良い方法です。

スレッドに関する Joe Albahari の Web サイトもチェックすることをお勧めします。これは、スレッドに関する非常に優れた入門書です。

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

于 2013-03-29T15:27:38.287 に答える