Visual Studioには「Background Worker」は、.NET2.0からある、いわゆる「コンポーネント」に入っているくらいの定番で、使いやすく、かんたんに非同期処理を実装するために役立つクラスです。
Microsoftの公式サイトの「方法 : バックグラウンド ワーカーを使用する」に書かれた内容通りにコードを書けば、簡単に実装できます。
ですが、ちょっと複雑なことをやろうとすると、途端に自由度がなく、やりにくくなります。
引数を渡したりとか、DoWorkイベントハンドラに、別のクラスのメソッドを仕込みたい場合などです。
DoWorkイベントハンドラから引数を渡すには?
たとえば、引数を渡したいことがあると思います。どうすれば良いでしょうか?
これには以下のようなアプローチがあります。
RunWorkerAsync()に引数を仕込む。
僕も知らなかったのですが、バックグラウンドワーカーを開始するメソッドに値を渡すことができます。
int value = 123;
backgroundWorker1.RunWorkerAsync(value);
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
int value = (int) e.Argument;
//なんらかの処理
double result = 0.1 * value;
e.Result = result;
}
ということは、なんでも渡せるということになります。オブジェクトなど、ジェネリッククラスも引数にできます。
List<object> arguments = new List<object>();
arguments.Add(argument 1);
arguments.Add(argument 1);
arguments.Add(argument n);
backgroundWorker1.RunWorkerAsync(arguments);
private void worker_DoWork(object sender, DoWorkEventArgs e) {
List<object> genericlist = e.Argument as List<object>;
}
この辺りのサンプルは、StackOverFlowで見ることができます。
メソッドごと渡す
では、ちょっとした引数ではなく、既にあるメソッドや、そこに含まれる引数も一緒に渡すにはどうすれば良いのでしょうか?
DoWorkイベントハンドラの記述に合わせず、そのままメソッドをクロージャ(ラムダ式)で渡す方法です。
backgroundWorker1.DoWork += (s, e) => MyWorkMethod(userName, targetNumber);
この正体は、以下のデリゲートメソッドでも表現できます。
backgroundWorker1.DoWork += delegate(object sender, DoWorkEventArgs e)
{
MyWorkMethod(userName, targetNumber);
};
これも同じくStackOverFlowにあった例です。
キャンセル処理はどうすれば良いの?
ただ、メソッドを渡す方法は分かっても、キャンセル処理を、渡したメソッド内で、どうやって行えば良いのでしょうか。
すでに書いたメソッドやクラスですと、BackgoundWorkerのために、あまり内容をグチャグチャと書き換えたりしたくはないものです。
ここは僕もずいぶんとハマって、なかなかネットでも見つからなかったのですが、DoWorkイベントのObject型の「Sender」と、DoWorkEventArgs型の「e」を、そのままメソッドに渡してやることで実現できました。
backgroundWorker1.DoWork += (s, e) => MyWorkMethod(s, e, userName, targetNumber);
上記の例ですと、以下の方が分かりやすいでしょうか。
backgroundWorker1.DoWork += delegate(object sender, DoWorkEventArgs e)
{
MyWorkMethod(sender, e, userName, targetNumber);
};
メソッド内ではこのように書きます。
public bool MyWorkMethod(object sender, DoWorkEventArgs e, string userName, int targetNumber)
{
BackgroundWorker worker = sender as BackgroundWorker;
//なんらかの処理…
//ユーザーによるキャンセルなど
if ((worker.CancellationPending == true))
{
e.Cancel = true;
return(false);
}
}
至極、当たり前で、単純なやり方ですが、調べて始めてから、気づくまでに時間がかかったので、一応ここにまとめて、シェアしておきます。