6

私が想定しているのは、かなり一般的なスレッド化シナリオです。

  • 完了しなければならない同じ仕事が 100 件あります
  • すべてのジョブは互いに独立しています
  • 一度に最大15件のジョブを処理したい
  • 各ジョブが完了すると、すべてのジョブが完了するまで新しいジョブが開始されます

各ジョブが完了時にイベントを発生させると仮定すると (私は BackgroundWorker クラスを使用しています)、これを実行する方法はいくつか考えられますが、「正しい」解決策が何であるかはわかりません。そこにいるあなたの教祖の何人かが私を正しい方向に向けることができることを望んでいました.

解決策 1:しばらくお待ち ください(続行) { Threading.Sleep(1000); Main()関数でループします。Job_Completed イベント ハンドラーのコードは、A)キューに入れられるジョブが残っておらず、B)キューに入れられたすべてのジョブが完了した場合に、continue = false を設定します。以前にこのソリューションを使用したことがありますが、うまく機能しているように見えます...私には少し「奇妙」に思えます。

解決策 2: Main() 関数で Application.Run() を使用します。同様に、Job_Completed イベント ハンドラーのコードは、A)キューに入れるジョブが残っていない場合、およびB)キューに入れられたすべてのジョブが完了した場合に、Application.Exit() を呼び出します。

解決策 3: ThreadPool を使用し、500 ~ 1000 のすべての要求をキューに入れ、一度に 10 個ずつ実行し (SetMaxThreads)、何らかの形ですべてが完了するのを待ちます。

これらすべてのソリューションの基本的な考え方は、ジョブがなくなるまで、別のジョブが完了するたびに新しいジョブが開始されるというものです。したがって、問題は既存のジョブが完了するのを待つだけでなく、保留中のジョブがなくなるまで待機することでもあります。ThreadPool が正しい解決策である場合、ThreadPool を待機してキューに入れられたすべてのアイテムを完了する正しい方法は何ですか?

ここでの最大の混乱は、Main() 関数内からイベントを発生させる方法を正確に理解していないことだと思います。どうやらそうらしいのですが、Windows メッセージ ループの観点からは、その仕組みがわかりません。この問題を解決する正しい方法は何ですか?またその理由は何ですか?

4

8 に答える 8

3

別のオプションが必要な場合は他の答えがいいですが(十分なオプションを持つことはできません)、これをアイデアとして考えてみてください。

各ジョブのデータをFIFOスタック内の構造体に配置するだけです。

15個のスレッドを作成します。

各スレッドはスタックから次のジョブを取得し、それをポップします。

スレッドが処理を終了したら、次のジョブを取得します。スタックが空の場合、スレッドは停止するか、単にスリープして待機します。

解決が非常に簡単な唯一の複雑さは、ポップをクリティカルセクションに配置することです(読み取り/ポップの同期)。

于 2009-04-28T02:56:19.950 に答える
2

Re: 「なんとか全部終わるのを待って」

ManualResetEventはあなたの友人です。大きなバッチを開始する前に、これらの子犬の 1 つを作成し、メイン スレッドで待機し、ジョブが完了したときにバックグラウンド操作の最後に設定します。

もう 1 つのオプションは、手動でスレッドを作成し、foreach スレッド thread.Join() を実行することです。

これを使用できます(テスト中に使用します)

     private void Repeat(int times, int asyncThreads, Action action, Action done) {
        if (asyncThreads > 0) {

            var threads = new List<Thread>();

            for (int i = 0; i < asyncThreads; i++) {

                int iterations = times / asyncThreads; 
                if (i == 0) {
                    iterations += times % asyncThreads;                    
                }

                Thread thread = new Thread(new ThreadStart(() => Repeat(iterations, 0, action, null)));
                thread.Start();
                threads.Add(thread);
            }

            foreach (var thread in threads) {
                thread.Join();
            }

        } else {
            for (int i = 0; i < times; i++) {
                action();
            }
        }
        if (done != null) {
            done();
        }
    }

使用法:

// Do something 100 times in 15 background threads, wait for them all to finish.
Repeat(100, 15, DoSomething, null)
于 2009-04-28T01:09:52.810 に答える
1

Task Parallel Library を使用するだけです。

これは、タスクで単一の単純な Parallel.For ループとして行うことができ、自動的にこれをかなりきれいに管理します。C# 4 と Microsoft の実装を待つことができない場合、一時的な回避策は、 TPL の Mono 実装をコンパイルして使用することです。(私は個人的には MS 実装、特に新しいベータ リリースを好みますが、Mono 実装は現在機能しており、再配布可能です。)

于 2009-04-28T01:09:58.113 に答える
1

作業項目をスレッド キューに入れると、waithandle が返されます。それらをすべて配列に入れて、WaitAll()関数に引数として渡すことができます。

于 2009-04-28T01:10:07.503 に答える
1

私は ThreadPool を使用します。

ジョブの実行を開始する前に、ManualResetEventと int カウンターを作成します。各ジョブを ThreadPool に追加し、そのたびにカウンターをインクリメントします。

各ジョブの終了時にカウンターをデクリメントし、カウンターがゼロになったら、イベントでSet()を呼び出します。

メイン スレッドで、WaitOne()を呼び出して、すべてのジョブが完了するのを待ちます。

于 2009-04-28T01:25:35.033 に答える
0

ThreadPool行く方法かもしれません。このSetMaxThreadsメソッドは、実行されるスレッドの数を制限することができます。ただし、これにより、プロセス/AppDomainのスレッドの最大数が制限されます。SetMaxThreadsプロセスがサービスとして実行されている場合は、使用することをお勧めしません。

private static ManualResetEvent manual = new ManualResetEvent(false);
private static int count = 0;

public void RunJobs( List<JobState> states )
{
     ThreadPool.SetMaxThreads( 15, 15 );

     foreach( var state in states )
     {
          Interlocked.Increment( count );
          ThreadPool.QueueUserWorkItem( Job, state );
     }

    manual.WaitOne();
}

private static void Job( object state )
{
    // run job
    Interlocked.Decrement( count );
    if( Interlocked.Read( count ) == 0 ) manual.Set();
}
于 2009-04-28T03:06:43.933 に答える
0

これが私がそれにアプローチする方法の擬似コードです(これはThreadPoolを活用していないため、誰かがより良い答えを持っているかもしれません:)

main
{
    create queue of 100 jobs
    create new array of 15 threads
    start threads, passing each the job queue
    do whatever until threads are done
}

thread(queue)
{
    while(queue isn't empty)
    {
        lock(queue) { if queue still isn't empty dequeue a thing }
        process the thing
    }

    queue is empty so exit thread
}

編集:スレッドがいつ終了したかを伝える方法が問題であり、(ThreadPooled スレッドではなく) 通常の C# スレッドを使用している場合は、オプションのタイムアウトを使用して各スレッドで Thread.Join() を呼び出すことができ、1 回だけ返されます。スレッドが完了しました。1 つのスレッドにハングアップすることなく、完了したスレッドの数を追跡したい場合は、次のような方法でスレッドを循環させることができます。

for(int i = 0; allThreads.Count > 0; i++)
{
    var thisThread = allThreads[i % threads.Count];
    if(thisThread.Join(timeout)) // something low, maybe 100 ms or something
        allThreads.Remove(thisThread);
}
于 2009-04-28T01:07:09.977 に答える