web-dev-qa-db-ja.com

作業用に複数のスレッドを生成し、すべてが完了するまで待機する

マルチスレッドタスクに関する「ベストプラクティス」に関するアドバイスが必要です。

例として、起動時にデータベースのさまざまな「タイプ」テーブルからデータを読み取り、アプリケーションに渡すコレクションに情報を保存するC#アプリケーションがあります。これにより、この情報が必要になるたびにデータベースにアクセスできなくなります。

現時点では、アプリケーションは10個のテーブルからデータを同期的に読み取っています。私は、アプリケーションがすべての並列で実行されている異なるスレッドの各テーブルから読み込まれるようにしたいと本当に思っています。アプリケーションは、すべてのスレッドが完了するのを待ってから、アプリケーションの起動を続行します。

backGroundWorkerを調べましたが、上記を達成するためのアドバイスが欲しいだけです。

  1. このメソッドは、アプリケーションの起動時間を短縮するために論理的に聞こえますか
  2. 各スレッドの作業は互いに独立していることを念頭に置いて、すべてのスレッドを最適に処理するには、すべてのスレッドが完了するのを待ってから続行する必要があります。

私はいくつかの答えを楽しみにしています

53
pharoc

これに対する私の好みは、単一のWaitHandleでこれを処理し、Interlockedを使用してカウンターのロックを回避することです。

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        ManualResetEvent resetEvent = new ManualResetEvent(false);
        int toProcess = numThreads;

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                    resetEvent.Set();
            }).Start();
        }

        // Wait for workers.
        resetEvent.WaitOne();
        Console.WriteLine("Finished.");
    }
}

これはうまく機能し、ロックを導入することなく、任意の数のスレッド処理に対応します。

91
Reed Copsey

@Reedのソリューションが好きです。 .NET 4.0で同じことを実現する別の方法は、 CountdownEvent を使用することです。

class Program
{
    static void Main(string[] args)
    {
        var numThreads = 10;
        var countdownEvent = new CountdownEvent(numThreads);

        // Start workers.
        for (var i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // Signal the CountdownEvent.
                countdownEvent.Signal();
            }).Start();
        }

        // Wait for workers.
        countdownEvent.Wait();
        Console.WriteLine("Finished.");
    }
}
28
Jack Leitch

Markが言うように、STAスレッドに対して64を超える待機ハンドルがある場合。スレッドでリストを作成し、2番目のループですべてが完了するのを待つことができます。

//check that all threads have completed.
foreach (Thread thread in threadList)
{
     thread.Join();

}  
9

.NET 4.0を使用していない場合、List < ManualResetEvent >、各スレッドに1つ、および Wait を使用して、 Set にすることができます=。複数のスレッドで待機するには、 WaitAll の使用を検討できますが、64の待機ハンドルの制限に注意してください。これ以上必要な場合は、それらをループして、それぞれを個別に待つことができます。

起動の高速化が必要な場合は、起動中にすべてのデータが読み取られるのを待つ必要はないでしょう。 GUIを表示するだけで、欠落している情報は、何らかの「更新中...」アイコンなどでグレー表示されます。情報が入ったら、イベントを発生させてGUIを更新します。すべてのテーブルからすべてのデータが読み込まれる前であっても、ユーザーが実行を開始できる操作は多数あります。

7
Mark Byers

冒険好きなら、C#4.0とTask Parallel Libraryを使用できます。

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});
6

複数の並列操作を待機する2つのパターンを次に示します。トリックは、メインスレッドを並列操作の1つであるかのように扱う必要があることです。それ以外の場合、ワーカースレッドでの完了の通知とメインスレッドからのその信号の待機の間には、わずかな競合状態があります。

int threadCount = 1;
ManualResetEvent finished = new ManualResetEvent(false);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  Interlocked.Increment(ref threadCount); 
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
          if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
      } 
  }); 
}
if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
finished.WaitOne(); 

個人的な好みとして、 CountdownEvent クラスを使用して、.NET 4.0で利用可能なカウントを行うのが好きです。

var finished = new CountdownEvent(1);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  finished.AddCount();
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
        finished.Signal();
      } 
  }); 
}
finished.Signal();
finished.Wait(); 

上記の例ではThreadPoolを使用していますが、任意のスレッド化メカニズムに交換できます。

4
Brian Gideon

[〜#〜] tpl [〜#〜] を使用する別の可能性は、jobsが処理するアイテムのコレクション、または実行するサブスレッドであると仮定します。

Task.WaitAll(jobs
    .Select(job => TaskFactory.StartNew(() => /*run job*/))
    .ToArray());
2
BartoszKP

楽しみのために、@ ReedがMonitorで行ったこと。 :P

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        int toProcess = numThreads;
        object syncRoot = new object();

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                {
                    lock (syncRoot)
                    {
                        Monitor.Pulse(syncRoot);
                    }
                }
            }).Start();
        }

        // Wait for workers.
        lock (syncRoot)
        {
            if (toProcess > 0)
            {
                Monitor.Wait(syncRoot);
            }
        }

        Console.WriteLine("Finished.");
    }
}
2
Andras Vass

データベースリーダースレッドが完了するとすぐに戻ると仮定すると、開始スレッドから順番に10個すべてのスレッドでThread.Joinを呼び出すことができます。

1
Marcelo Cantos

.NET 3.5以前を使用している場合は、AsyncResultまたはBackgroundWorkerの配列を使用して、返されたスレッドの数をカウントできます(インターロック操作でカウンターを減らすことを忘れないでください)( http:// www .albahari.com/threading / 参照)。

.NET 4.0を使用している場合、並列処理が最も簡単なアプローチです。

0
Danny Varod

私が使用したい簡単な方法:

    private int ThreadsCount = 100; //initialize threads count
    private void button1_Click(object sender, EventArgs e)
    {   
        for (int i = 0; i < ThreadsCount; i++)
        {
            Thread t = new Thread(new ThreadStart(myMethod));
            t.IsBackground = true;
            t.Start(); 
        } 
    }

    private void myMethod()
    {
        //everytime a thread finishes executing decrease the threads count
        ThreadsCount = ThreadsCount - 1;

        if (ThreadsCount < 1)
        {
            //if all threads finished executing do whatever you wanna do here..
            MessageBox.Show("Finished Executing all threads!!!");
        }
    }
0
SolidSnake

他の人を助けるために投稿することは、私が思いついたような解決策を探すのにかなりの時間を費やした。だから私は少し異なるアプローチを取りました。スレッドが開始および停止すると、多数のスレッドをスピンオフし、カウンターをインクリメントし、カウンターをデクリメントしました。それからメインメソッドでは、スレッドが完了するのを一時停止して待ちたいと思っていました。

while (threadCounter > 0)
{
    Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}

私のブログに記載されています。 http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

0
Adam