私は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();
}
}
}
['Parallel.For'](https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.for.aspx)を使用できますか? – Blorgbeard
あなたの質問はあまりにも幅があり、あまりにも広すぎるにもかかわらず、尋ねられた他の多くのスタックオーバーフローの質問によって率直に言及されています。 'BackgroundWorker'に相当するのは' Task.Run() '(' DoWork'イベントを置き換える)と 'Progress'( 'ProgressChanged'イベントを置き換える)です。 'async' /' await'を使うと、 'RunWorkerCompleted'機能も得られます。必要に応じて 'BackgroundWorker'を使用することができます。すべての手法はスレッドプールを使用するようにデフォルト設定されているため、既存のコアを同等にスケジューリングします。必要に応じて、TPLベースのソリューションではこれ以上の制御が可能です。 –
こんにちはすべて、 私はBlorgbeadとPeter Dunihoに感謝しています。ありがとうございました。 今、@#$%^&! 一般的なアーキテクチャの質問にどのように広い質問なしで質問しますか? **私は質問を繰り返します:** - バックグラウンドワーカーは、このようなCPU集約的なタスクに使用する正しいテクニックですか? - そうでない場合は、どのような手法が優れていますか? - このようなCPU集約的なタスクの良い例がありますか? - バックグラウンドワーカーを使用するとどのようなリスクがありますか? よろしくお願いいたします。 Greg Harris –