2016-08-18 22 views
-3

私はWinFormsのC#プログラムを使用して、ユーザーのマシン上で(メモリ内で)最大100万のビジネスオブジェクトを開くことができます。別々のCPUコアでタスクを並列実行する方法

私のマネージャは、これらのビジネスオブジェクトに対して実際の単純なフィルタを要求しています。 "Fred"をフィルタリングすると、ユーザーは任意のテキストフィールド(名前、住所、担当者など)に "Fred"を含むすべてのオブジェクトのリストを表示します。 また、これは、UIをブロックすることなく、できるだけリアルタイムに近い必要があります。 "Fred"と入力した場合は、 "F"を入力するとすぐに検索が開始され、テキストフィールドに "F"の検索結果が表示されます(最小値検索では3文字)。 テキストボックスを "Fr"に変更すると、古い検索が停止され(実行中の場合)、新しい検索が開始されます。

これは、IOがゼロのユーザーのローカルマシンでCPUを大量に使用する操作です。 これは私のCPU上の別々のコア上の別々のスレッド上で実行するために別々のタスクを起動する必要があるようです。 すべて完了したら、結果を1つのリストに戻して結果をユーザーに表示します。

私は古い学校ですが、これはBackgroundWorkerの仕事のようですが、BackgroundWorkerが明示的に.NET 4.5(悲しい顔)で廃止とラベル付けされていることを読んでいます。参照してください:Async/await vs BackgroundWorker

私はBackgroundWorkerを新しいasync cwaコマンドで置き換えるべきだと言う記事がたくさんあります。

しかし、この例はあまりよくありません。「async awaitは別のスレッドを保証しません」という行に沿ったコメントがあります。また、すべての例では待機中のタスク(CPU集約タスクではありません) )。

よく似たCPU集約タスクである素数を探して、私はその周りを遊んで、私の必要性のほとんどを満たすことがわかった、良い例のBackgroundWorkerを見つけました。しかし、私はBackgroundWorkerが.NET 4.5では廃止されているという問題があります。 BackgroundWorkerの調査から

私の調査結果は以下のとおりです。あなたがマシン上で 物理コアごとに1つのタスクがある場合

  • ベストパフォーマンスの改善が得られる、私のVMが3つのコアを持って、タスクが 最速3背景労働者と走りましたタスク。
  • のバックグラウンドワーカータスクが多すぎるとパフォーマンスが低下します。
  • のUIスレッドに進捗通知が多すぎると、パフォーマンスが低下します。

質問:

が背景労働者がこのようなCPU集約型のタスクのために使用する権利技法ですか? そうでない場合は、どのような手法が優れていますか? このようなCPU集約的なタスクの良い例はありますか? バックグラウンドワーカーを使用するとどのようなリスクが発生しますか?単一の背景労働者

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Diagnostics; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

// This code is based on code found at: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b3650421-8761-49d1-996c-807b254e094a/c-backgroundworker-for-progress-dialog?forum=csharpgeneral 
// Well actually at: http://answers.flyppdevportal.com/MVC/Post/Thread/e98186b1-8705-4840-ad39-39ac0bdd0a33?category=csharpgeneral 

namespace PrimeNumbersWithBackgroundWorkerThread 
{ 
    public partial class Form_SingleBackground_Worker : Form 
    { 
    private const int  _MaxValueToTest = 300 * 1000; 
    private const int  _ProgressIncrement = 1024 * 2 ; // How often to display to the UI that we are still working 
    private BackgroundWorker _Worker; 
    private Stopwatch  _Stopwatch; 
    public Form_SingleBackground_Worker() 
    { 
     InitializeComponent(); 
    } 
    private void btn_Start_Click   (object sender, EventArgs e) 
    { 
     if (_Worker == null) 
     { 
     progressBar.Maximum     = _MaxValueToTest; 
     txt_Output.Text      = "Started"; 
     _Stopwatch       = Stopwatch.StartNew(); 
     _Worker        = new BackgroundWorker(); 
     _Worker.WorkerReportsProgress  = true; 
     _Worker.WorkerSupportsCancellation = true; 
     _Worker.DoWork      += new DoWorkEventHandler   (worker_DoWork   ); 
     _Worker.ProgressChanged   += new ProgressChangedEventHandler (worker_ProgressChanged ); 
     _Worker.RunWorkerCompleted   += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); 
     _Worker.RunWorkerAsync(_MaxValueToTest); // do the work 
     } 
    } 
    private void btn_Cancel_Click   (object sender, EventArgs e) 
    { 
     if (_Worker != null && _Worker.IsBusy) 
     { 
     _Worker.CancelAsync(); 
     } 
    } 
    private void worker_DoWork    (object sender, DoWorkEventArgs e) 
    { 
     int    lMaxValueToTest = (int)e.Argument; 
     BackgroundWorker lWorker   = (BackgroundWorker)sender; // BackgroundWorker running this code for Progress Updates and Cancelation checking 
     List<int>  lResult   = new List<int>(); 
     long    lCounter   = 0; 

     //Check all uneven numbers between 1 and whatever the user choose as upper limit 
     for (int lTestValue = 1; lTestValue < lMaxValueToTest; lTestValue += 2) 
     { 
     lCounter++; 
     if (lCounter % _ProgressIncrement == 0) 
     { 
      lWorker.ReportProgress(lTestValue); // Report progress to the UI every lProgressIncrement tests (really slows down if you do it every time through the loop) 
      Application.DoEvents(); 

      //Check if the Cancelation was requested during the last loop 
      if (lWorker.CancellationPending) 
      { 
      e.Cancel = true; //Tell the Backgroundworker you are canceling and exit the for-loop 
      e.Result = lResult.ToArray(); 
      return; 
      } 
     } 

     bool lIsPrimeNumber = IsPrimeNumber(lTestValue); //Determine if lTestValue is a Prime Number 
     if (lIsPrimeNumber) 
      lResult.Add(lTestValue); 
     } 
     lWorker.ReportProgress(lMaxValueToTest); // Tell the progress bar you are finished 
     e.Result = lResult.ToArray();    // Save Return Value 
    } 
    private void worker_ProgressChanged (object sender, ProgressChangedEventArgs e) 
    { 
     int lNumber  = e.ProgressPercentage; 
     txt_Output.Text = $"{lNumber.ToString("#,##0")} ({(lNumber/_Stopwatch.ElapsedMilliseconds).ToString("#,##0")} thousand per second)"; 
     progressBar.Value = lNumber; 
     Refresh(); 
    } 
    private void worker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e) 
    { 
     progressBar.Value = progressBar.Maximum; 
     Refresh(); 

     if (e.Cancelled) 
     { 
     txt_Output.Text = "Operation canceled by user"; 
     _Worker   = null; 
     return; 
     } 
     if (e.Error != null) 
     { 
     txt_Output.Text = $"Error: {e.Error.Message}"; 
     _Worker   = null; 
     return; 
     } 
     int[] lIntResult = (int[])e.Result; 
     string lStrResult = string.Join(", ", lIntResult); 
     string lTimeMsg = $"Calculate all primes up to {_MaxValueToTest.ToString("#,##0")} with \r\nSingle Background Worker with only 1 worker: Total duration (seconds): {_Stopwatch.ElapsedMilliseconds/1000}"; 
     txt_Output.Text = $"{lTimeMsg}\r\n{lStrResult}"; 
     _Worker   = null; 
    } 
    private bool IsPrimeNumber    (long aValue) 
    { 
     // see https://en.wikipedia.org/wiki/Prime_number 
     // Among the numbers 1 to 6, the numbers 2, 3, and 5 are the prime numbers, while 1, 4, and 6 are not prime. 
     if (aValue <= 1) return false; 
     if (aValue == 2) return true ; 
     if (aValue == 3) return true ; 
     if (aValue == 4) return false; 
     if (aValue == 5) return true ; 
     if (aValue == 6) return false; 
     bool  lIsPrimeNumber = true; 
     long  lMaxTest  = aValue/2 + 1; 
     for (long lTest   = 3; lTest < lMaxTest && lIsPrimeNumber; lTest += 2) 
     { 
     long lMod = aValue % lTest; 
     lIsPrimeNumber = lMod != 0; 
     } 
     return lIsPrimeNumber; 
    } 
    } 
} 

コード例に基づいて

コード例複数の背景労働者

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Diagnostics; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

// This code is based on code found at: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b3650421-8761-49d1-996c-807b254e094a/c-backgroundworker-for-progress-dialog?forum=csharpgeneral 
// Well actually at: http://answers.flyppdevportal.com/MVC/Post/Thread/e98186b1-8705-4840-ad39-39ac0bdd0a33?category=csharpgeneral 

namespace PrimeNumbersWithBackgroundWorkerThread 
{ 
    public partial class Form_MultipleBackground_Workers : Form 
    { 
    private const int    _MaxValueToTest = 300 * 1000; 
    private const int    _ProgressIncrement = 1024 * 2 ; // How often to display to the UI that we are still working 
    private int     _NumberOfChuncks = 2   ; // Best performance looks to be when this value is same as the number of cores 
    private List<BackgroundWorker> _Workers   = null  ; 
    private List<WorkChunk>  _Results   = null  ; 
    private Stopwatch    _Stopwatch; 
    public Form_MultipleBackground_Workers() { InitializeComponent(); } 
    private void btn_Start_Click   (object sender, EventArgs e) 
    { 
     if (_Workers == null) 
     { 
     progressBar.Maximum = _MaxValueToTest; 
     txt_Output.Text  = "Started"; 
     _Stopwatch   = Stopwatch.StartNew(); 
     _Workers    = new List<BackgroundWorker>(); 
     _Results    = new List<WorkChunk>(); 
     int lChunckSize  = _MaxValueToTest/_NumberOfChuncks; 
     int lChunckStart  = 1; 
     while (lChunckStart <= _MaxValueToTest) 
     { 
      int lChunckEnd = lChunckStart + lChunckSize; 
      if (lChunckEnd > _MaxValueToTest) lChunckEnd = _MaxValueToTest; 
      BackgroundWorker lWorker = StartAWorker(lChunckStart, lChunckEnd); 
      _Workers.Add(lWorker); 
      lChunckStart += lChunckSize + 1; 
     } 
     } 
    } 
    private BackgroundWorker StartAWorker (int aRangeStart, int aRangeEnd) 
    { 
     WorkChunk  lWorkChunk   = new WorkChunk() { StartRange = aRangeStart, EndRange = aRangeEnd }; 
     BackgroundWorker lResult   = new BackgroundWorker(); 
     lResult.WorkerReportsProgress  = true; 
     lResult.WorkerSupportsCancellation = true; 
     lResult.DoWork      += new DoWorkEventHandler   (worker_DoWork   ); 
     lResult.ProgressChanged   += new ProgressChangedEventHandler (worker_ProgressChanged ); 
     lResult.RunWorkerCompleted   += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); 
     lResult.RunWorkerAsync(lWorkChunk); // do the work 
     Console.WriteLine(lWorkChunk.ToString()); 
     return lResult; 
    } 
    private void btn_Cancel_Click   (object sender, EventArgs e) 
    { 
     if (_Workers != null) 
     { 
     foreach(BackgroundWorker lWorker in _Workers) 
     { 
      if (lWorker.IsBusy) 
      lWorker.CancelAsync(); 
     } 
     } 
    } 
    private void worker_DoWork    (object sender, DoWorkEventArgs e) 
    { 
     WorkChunk  lWorkChunk   = (WorkChunk)e.Argument; 
     BackgroundWorker lWorker   = (BackgroundWorker)sender; // BackgroundWorker running this code for Progress Updates and Cancelation checking 
     int    lCounter   = 0; 
     e.Result = lWorkChunk; 
     lWorkChunk.StartTime = DateTime.Now; 
     lWorkChunk.Results = new List<int>(); 

     // Check all uneven numbers in range 
     for (int lTestValue = lWorkChunk.StartRange; lTestValue <= lWorkChunk.EndRange; lTestValue++) 
     { 
     lCounter++; 
     if (lCounter % _ProgressIncrement == 0) 
     { 
      lWorker.ReportProgress(lCounter); // Report progress to the UI every lProgressIncrement tests (really slows down if you do it every time through the loop) 
      Application.DoEvents();   // This is needed for cancel to work 
      if (lWorker.CancellationPending) // Check if Cancelation was requested 
      { 
      e.Cancel = true; //Tell the Backgroundworker you are canceling and exit the for-loop 
      lWorkChunk.EndTime = DateTime.Now; 
      return; 
      } 
     } 

     bool lIsPrimeNumber = IsPrimeNumber(lTestValue); //Determine if lTestValue is a Prime Number 
     if (lIsPrimeNumber) 
      lWorkChunk.Results.Add(lTestValue); 
     } 
     lWorker.ReportProgress(lCounter); // Tell the progress bar you are finished 
     lWorkChunk.EndTime = DateTime.Now; 
    } 
    private void worker_ProgressChanged (object sender, ProgressChangedEventArgs e) 
    { 
     int lNumber  = e.ProgressPercentage; 
     txt_Output.Text = $"{lNumber.ToString("#,##0")} ({(lNumber/_Stopwatch.ElapsedMilliseconds).ToString("#,##0")} thousand per second)"; 
     progressBar.Value = lNumber; 
     Refresh(); 
    } 
    private void worker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e) 
    { 
     // All threads have to complete before we have real completion 
     progressBar.Value = progressBar.Maximum; 
     Refresh(); 

     if (e.Cancelled) 
     { 
     txt_Output.Text = "Operation canceled by user"; 
     _Workers  = null; 
     return; 
     } 
     if (e.Error != null) 
     { 
     txt_Output.Text = $"Error: {e.Error.Message}"; 
     _Workers  = null; 
     return; 
     } 
     WorkChunk lPartResult = (WorkChunk)e.Result; 
     Console.WriteLine(lPartResult.ToString()); 
     _Results.Add(lPartResult); 
     if (_Results.Count == _NumberOfChuncks) 
     { 
     // All done, all threads are back 
     _Results = (from X in _Results orderby X.StartRange select X).ToList(); // Make sure they are all in the right sequence 
     List<int> lFullResults = new List<int>(); 
     foreach (WorkChunk lChunck in _Results) 
     { 
      lFullResults.AddRange(lChunck.Results); 
     } 
     string lStrResult = string.Join(", ", lFullResults); 
     string lTimeMsg = $"Calculate all primes up to {_MaxValueToTest.ToString("#,##0")} with \r\nMultiple Background Workers with {_NumberOfChuncks} workers: Total duration (seconds): {_Stopwatch.ElapsedMilliseconds/1000}"; 
     txt_Output.Text = $"{lTimeMsg}\r\n{lStrResult}"; 
     _Workers = null; 
     } 
    } 
    private bool IsPrimeNumber    (long aValue) 
    { 
     // see https://en.wikipedia.org/wiki/Prime_number 
     // Among the numbers 1 to 6, the numbers 2, 3, and 5 are the prime numbers, while 1, 4, and 6 are not prime. 
     if (aValue <= 1) return false; 
     if (aValue == 2) return true ; 
     if (aValue == 3) return true ; 
     if (aValue == 4) return false; 
     if (aValue == 5) return true ; 
     if (aValue == 6) return false; 
     bool  lIsPrimeNumber = true; 
     long  lMaxTest  = aValue/2 + 1; 
     for (long lTest   = 2; lTest < lMaxTest && lIsPrimeNumber; lTest++) 
     { 
     long lMod = aValue % lTest; 
     lIsPrimeNumber = lMod != 0; 
     } 
     return lIsPrimeNumber; 
    } 
    } 
    public class WorkChunk 
    { 
    public int  StartRange { get; set; } 
    public int  EndRange { get; set; } 
    public List<int> Results { get; set; } 
    public string Message { get; set; } 
    public DateTime StartTime { get; set; } = DateTime.MinValue; 
    public DateTime EndTime { get; set; } = DateTime.MinValue; 
    public override string ToString() 
    { 
     StringBuilder lResult = new StringBuilder(); 
     lResult.Append($"WorkChunk: {StartRange} to {EndRange}"); 
     if (Results == null     ) lResult.Append(", no results yet"); else lResult.Append($", {Results.Count} results"); 
     if (string.IsNullOrWhiteSpace(Message)) lResult.Append(", no message" ); else lResult.Append($", {Message}"); 
     if (StartTime == DateTime.MinValue  ) lResult.Append(", no start time" ); else lResult.Append($", Start: {StartTime.ToString("HH:mm:ss.ffff")}"); 
     if (EndTime == DateTime.MinValue  ) lResult.Append(", no end time" ); else lResult.Append($", End: { EndTime .ToString("HH:mm:ss.ffff")}"); 
     return lResult.ToString(); 
    } 
    } 
} 
+0

['Parallel.For'](https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.for.aspx)を使用できますか? – Blorgbeard

+1

あなたの質問はあまりにも幅があり、あまりにも広すぎるにもかかわらず、尋ねられた他の多くのスタックオーバーフローの質問によって率直に言及されています。 'BackgroundWorker'に相当するのは' Task.Run() '(' DoWork'イベントを置き換える)と 'Progress '( 'ProgressChanged'イベントを置き換える)です。 'async' /' await'を使うと、 'RunWorkerCompleted'機能も得られます。必要に応じて 'BackgroundWorker'を使用することができます。すべての手法はスレッドプールを使用するようにデフォルト設定されているため、既存のコアを同等にスケジューリングします。必要に応じて、TPLベースのソリューションではこれ以上の制御が可能です。 –

+0

こんにちはすべて、 私はBlorgbeadとPeter Dunihoに感謝しています。ありがとうございました。 今、@#$%^&! 一般的なアーキテクチャの質問にどのように広い質問なしで質問しますか? **私は質問を繰り返します:** - バックグラウンドワーカーは、このようなCPU集約的なタスクに使用する正しいテクニックですか? - そうでない場合は、どのような手法が優れていますか? - このようなCPU集約的なタスクの良い例がありますか? - バックグラウンドワーカーを使用するとどのようなリスクがありますか? よろしくお願いいたします。 Greg Harris –

答えて

1

に基づいて、私は、オープンまでに100万個の、ビジネス・オブジェクトを持っています1回

もちろんですが、と表示されないように、を一度に多数表示できます。

また、これは、UIをブロックすることなくできるだけリアルタイムに近い必要があります。

最初に確認するのは、すでに十分速いかどうかです。合理的なハードウェア上に現実的な数のオブジェクトがある場合、UIスレッドで十分に高速にフィルタリングできますか?それが十分に速ければ、より速くする必要はありません。

私はBackgroundWorkerを新しいasync await c#コマンドに置き換えるべきだと言う記事がたくさんあります。

asyncは、BackgroundWorkerの代わりではありません。ただし、Task.Runはです。私はhow Task.Run is superior to BackgroundWorkerを記述するブログ投稿シリーズを持っています。

UIスレッドに進捗通知が多すぎるとパフォーマンスが低下します。

私はこれをUIレイヤーで解決することをお勧めします。ObserverProgressなどです。

バックグラウンドワーカーは、このようなCPU集約的なタスクに使用する適切なテクニックですか?

マルチスレッドソリューションにジャンプする前に、仮想化を先に検討してください。私が最初に述べたように、あなたはおそらくを表示することができない多くの項目がです。したがって、を表示するまで、フィルタを実行しないのはなぜですか?また、ユーザーがスクロールした場合は、フィルターをもう一度実行してください。

どのような技術が優れていますか?

私はお勧め:

  1. テストを最初に。 UIスレッドのすべての項目をフィルタリングするのに十分速い場合は、すでに完了しています。
  2. 仮想化を実装します。フィルタリングすべての項目が遅すぎる場合でも、表示するのに十分な時間がかかるまでは、の一部の項目のみがの項目になります。
  3. 上記のいずれでも十分に高速でない場合は、仮想化に加えてTask.RunObserverProgress)を使用してください。