2017-02-07 5 views
1

クエリを非同期で実行するWindowsフォームツールを構築しようとしています。 このアプリケーションには、実行可能な30のクエリを含むdatagridviewがあります。ユーザーは、実行したいクエリー、例えば10個のクエリーをチェックし、ボタンを押す。 このアプリケーションには、クエリを非同期で実行するために使用できるスレッドの数を示すmaxthreads = 3という変数があります(説明のため)。クエリは実稼働環境で実行されるため、同時に実行されるスレッドが多すぎるためシステムに過負荷をかけたくありません。各クエリは平均30秒間実行されます。 (約5分、その他2秒) datagridviewには、各クエリーのステータスを示すアイコンを含むイメージ列があります(0-実行可能、1-選択、2-実行、3-正常に完了しました-1エラー) クエリが開始して終了するたびにUIと通信できる必要があります。クエリが終了すると、結果はTabControl(クエリごとに1つのタブ)に含まれるデータグリッドビューに表示されます複数のクエリをCで実行するマルチスレッドアプリケーションを作成する

アプローチ:私はいくつかのmaxthreadバックグラウンドワーカーを作成してクエリを実行させることを考えていました。バックグラウンド作業者が終了すると、UIと通信し、すべてのクエリが実行されるまで新しいクエリなどに割り当てられます。

作業をバックグラウンドワーカーにディスパッチするが、すべてのスレッドが終了するまで待つ方法はわからないassignmentWorkerを使ってみました。 bgwが終了すると、RunWorkerCompletedイベントの進行状況をassignmentWorkerに報告しますが、それは既に完了しています。私は実行する必要があるすべてのクエリに割り当て労働者を呼び出すUIスレッドで

:すべてが同じイベントを実行BGW

private void assignmentWorker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     foreach (Query q in (List<Query>)e.Argument) 
     { 
      while (!q.Processed) 
      { 
       for (int threadNum = 0; threadNum < maxThreads; threadNum++) 
       { 
        if (!threadArray[threadNum].IsBusy) 
        { 
         threadArray[threadNum].RunWorkerAsync(q); 
         q.Processed = true; 
         assignmentWorker.ReportProgress(1, q); 
         break; 
        } 
       } 

       //If all threads are being used, sleep awhile before checking again 
       if (!q.Processed) 
       { 
        Thread.Sleep(500); 
       } 
      } 
     } 
    } 

:ここ

private void btnRunQueries_Click(object sender, EventArgs e) 
    { 
     if (AnyQueriesSelected()) 
     { 
      tcResult.TabPages.Clear(); 

      foreach (DataGridViewRow dgr in dgvQueries.Rows) 
      { 
       if (Convert.ToBoolean(dgr.Cells["chk"].Value)) 
       { 
        Query q = new Query(dgr.Cells["ID"].Value.ToString(), 
         dgr.Cells["Name"].Value.ToString(), 
         dgr.Cells["FileName"].Value.ToString(), 
         dgr.Cells["ShortDescription"].Value.ToString(), 
         dgr.Cells["LongDescription"].Value.ToString(), 
         dgr.Cells["Level"].Value.ToString(), 
         dgr.Cells["Task"].Value.ToString(), 
         dgr.Cells["Importance"].Value.ToString(), 
         dgr.Cells["SkillSet"].Value.ToString(), 
         false, 
         new Dictionary<string, string>() 
         { { "#ClntNb#", txtClntNum.Text }, { "#Staff#", "100300" } }); 

        qryList.Add(q); 
       } 
      } 
      assignmentWorker.RunWorkerAsync(qryList); 
     } 
     else 
     { 
      MessageBox.Show("Please select at least one query.", 
          "Warning", 
          MessageBoxButtons.OK, 
          MessageBoxIcon.Information); 
     } 
    } 

はAssignmentWorkerある

private void backgroundWorkerFiles_DoWork(object sender, DoWorkEventArgs e) 
    { 
     try 
     { 
      Query qry = (Query)e.Argument; 

      DataTable dtNew = DataAccess.RunQuery(qry).dtResult; 

      if (dsQryResults.Tables.Contains(dtNew.TableName)) 
      { 
       dsQryResults.Tables.Remove(dtNew.TableName); 
      } 

      dsQryResults.Tables.Add(dtNew); 

      e.Result = qry; 
     } 
     catch (Exception ex) 
     { 

     } 
    } 

クエリが返され、DataTableがデータセットに追加されたら:

private void backgroundWorkerFiles_RunWorkerCompleted(object sender, 
                RunWorkerCompletedEventArgs e) 
    { 
     try 
     { 
      if (e.Error != null) 
      { 
       assignmentWorker.ReportProgress(-1, e.Result); 
      } 
      else 
      { 
       assignmentWorker.ReportProgress(2, e.Result); 
      } 
     } 
     catch (Exception ex) 
     { 
      int o = 0; 
     } 
    } 

私が持っている問題は、割り当て作業員がBGW仕上げの前に終了し、assignmentWorker.ReportProgressへの呼び出しは、(私のフランス語を言い訳)地獄に行くということです。 割り当てワーカーを終了する前に、起動したすべてのbgwが終了するのを待つ方法はありますか?

ありがとうございました!

+1

私はバックグラウンドワーカーと割り当てワーカーに分割しません。このタスクは複雑すぎます。実行するクエリの 'foreach'がThreadPoolに作業を開始させるか、または動作スレッドの数が' maxthreads'以下になるのを待つバックグラウンドスレッドを持つことができます。処理するすべてのクエリが完了するまでループします。結果を表示するには、バックグラウンドタスクは、 'Dispatcher'を使ってメインUIスレッドで作業を開始し、UIを適切に更新して終了する必要があります。 – Mobigital

答えて

1

the comment aboveに記載されているように、デザインが複雑すぎます。並行して実行する必要がある特定の最大数のタスク(クエリ)がある場合は、その数のワーカーを作成して、そのキューが空になるまでタスクのキュー(またはリスト)からタスクを消費させることができます。

あなたの特定のシナリオを簡潔に明確に示しているように、Minimal, Complete, and Verifiable code exampleが不十分な場合は、質問に直接対処するコードを提供することは現実的ではありません。あなたのケースでは

using System; 
using System.Collections.Generic; 
using System.Threading.Tasks; 

namespace TestSO42101517WaitAsyncTasks 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Random random = new Random(); 
      int maxTasks = 30, 
       maxActive = 3, 
       maxDelayMs = 1000, 
       currentDelay = -1; 
      List<TimeSpan> taskDelays = new List<TimeSpan>(maxTasks); 

      for (int i = 0; i < maxTasks; i++) 
      { 
       taskDelays.Add(TimeSpan.FromMilliseconds(random.Next(maxDelayMs))); 
      } 

      Task[] tasks = new Task[maxActive]; 
      object o = new object(); 

      for (int i = 0; i < maxActive; i++) 
      { 
       int workerIndex = i; 

       tasks[i] = Task.Run(() => 
       { 
        DelayConsumer(ref currentDelay, taskDelays, o, workerIndex); 
       }); 
      } 

      Console.WriteLine("Waiting for consumer tasks"); 

      Task.WaitAll(tasks); 

      Console.WriteLine("All consumer tasks completed"); 
     } 

     private static void DelayConsumer(ref int currentDelay, List<TimeSpan> taskDelays, object o, int workerIndex) 
     { 
      Console.WriteLine($"worker #{workerIndex} starting"); 

      while (true) 
      { 
       TimeSpan delay;  
       int delayIndex; 

       lock (o) 
       { 
        delayIndex = ++currentDelay; 
        if (delayIndex < taskDelays.Count) 
        { 
         delay = taskDelays[delayIndex]; 
        } 
        else 
        { 
         Console.WriteLine($"worker #{workerIndex} exiting"); 
         return; 
        } 
       } 

       Console.WriteLine($"worker #{workerIndex} sleeping for {delay.TotalMilliseconds} ms, task #{delayIndex}"); 
       System.Threading.Thread.Sleep(delay); 
      } 
     } 
    } 
} 

は、各労働者がいくつかのグローバル状態への進行状況を報告する:私は上記のようしかし、ここでは動作しますしList<T>としてあなたの元のコードを、使用例です。あなたの "割り当て"ワーカーにはReportProgressハンドラが表示されないので、具体的にどのように表示されるかは言えません。おそらく、-1または2のいずれかの値を処理する方法(つまり、あなたのReportProgressハンドラは何だったでしょうか)を知っている何らかの方法にそれを渡すことが考えられます。

タスクに実際のキューデータ構造を使用する場合は、特に個々のタスクが使用される場合は、コードをいくらか簡略化できます。そのアプローチは次のようになります。

using System; 
using System.Collections.Concurrent; 
using System.Threading.Tasks; 

namespace TestSO42101517WaitAsyncTasks 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Random random = new Random(); 
      int maxTasks = 30, 
       maxActive = 3, 
       maxDelayMs = 1000, 
       currentDelay = -1; 
      ConcurrentQueue<TimeSpan> taskDelays = new ConcurrentQueue<TimeSpan>(); 

      for (int i = 0; i < maxTasks; i++) 
      { 
       taskDelays.Enqueue(TimeSpan.FromMilliseconds(random.Next(maxDelayMs))); 
      } 

      Task[] tasks = new Task[maxActive]; 

      for (int i = 0; i < maxActive; i++) 
      { 
       int workerIndex = i; 

       tasks[i] = Task.Run(() => 
       { 
        DelayConsumer(ref currentDelay, taskDelays, workerIndex); 
       }); 
      } 

      Console.WriteLine("Waiting for consumer tasks"); 

      Task.WaitAll(tasks); 

      Console.WriteLine("All consumer tasks completed"); 
     } 

     private static void DelayConsumer(ref int currentDelayIndex, ConcurrentQueue<TimeSpan> taskDelays, int workerIndex) 
     { 
      Console.WriteLine($"worker #{workerIndex} starting"); 

      while (true) 
      { 
       TimeSpan delay; 

       if (!taskDelays.TryDequeue(out delay)) 
       { 
        Console.WriteLine($"worker #{workerIndex} exiting"); 
        return; 
       } 

       int delayIndex = System.Threading.Interlocked.Increment(ref currentDelayIndex); 

       Console.WriteLine($"worker #{workerIndex} sleeping for {delay.TotalMilliseconds} ms, task #{delayIndex}"); 
       System.Threading.Thread.Sleep(delay); 
      } 
     } 
    } 
} 
関連する問題