C# で Task を使う

C# もいろいろ進化して、最近ではBackgroundWorkerの代わりにTaskというものが登場してきていますね。これは、BackgroundWorkerよりもコーディング量が少なくて同じようなことが実現できるので便利なのですが、巷の説明ブログを読んでいると、なんだか間違った解説をもっともらしく発表している残念なサイトが多く、実際の使用を想定したサンプルも少ないような気がしたので、ここでサンプルとともにまとめておきたいと思います。

サンプルプログラム(スケルトン)

まずは、プログラム全体を見られるようにスケルトンの状態でソースを公開します。このプログラムは、Windowsのあるフォームのソースです。フォームロードイベント(FormTest_Load)が起点となり、フォームが画面に表示される際に、サーバなどからデータを取ってきて画面に表示するようなことを想定しています。

 

プログラムの流れ

プログラムの流れを見ていくと、以下のようになります。

  1. フォームロードイベントが発行されFormTest_Load関数が呼び出されます。
  2. FormTest_Load関数の中ですぐに時間がかかる処理(バックグランド処理)を呼び出す役目の関数(MyTestFunction)が呼び出されます。
  3. MyTestFunctionは、実際に処理に時間がかかる関数(MyBackgroundTestFunction)をバックグランド処理として実行します。

では、個別の処理を見ていきたいと思います。

バックグランド処理を起動する関数

 上記のサンプルでは「private async void MyTestFunction()」がこれに相当します。この処理の中では、後述します「await 」句を使用しているため、このawait句がある関数の宣言では「async」を付ける必要があります。
 もしも、フォームロードイベントの関数の中で直接MyTestFunction関数の内容を書きたい場合は、フォームロードイベントハンドラの関数の定義の部分で「async」を宣言します。

Progress<string> progress = new Progress<string>(onProgressChanged);

バックグランドで実行中の処理から処理の状況を報告するためのしくみがProgressです。ここではstring型の引数を1つ取るプログレス用の関数を定義しておきます。引数は通常の関数と同じで、いくつでも設定できます。

Task<string> task = Task.Run<string>(() => MyBackgroundTestFunction(progress));

これがバックグランドプロセスを起動している部分です。
このバックグランドプロセスはstring型の戻り値を返し、引数としてProgress型のデータを1つ定義しています。引数はいくつでも追加できます。

string result = await task;

この部分に到達すると、処理が一旦戻ります。ちょうどReturnで関数から戻るようなイメージです。ですので、FormTest_Loadのようにasyncの関数を呼び出している関数のようば場合注意が必要ですFormTest_Loadの中でMyTestFunctionを呼び出した後に処理を入れないようにします。そうしないとawait句で一時的に戻ってきたときに、その処理が実行サれてしまうからです。
await句の動作は以下のようになります。

sample-diagram

 まず、Task.Runによってバックグランドプロセスが起動されます(青の線)。

 次に、呼び出し元のほうでは、await句によってReturnと同じ動作をし、MyTestFunction関数から抜け、さらにFormTest_Loadからも抜けて、メッセージループへ戻ります(赤い線)。

 バックグランドの処理(MyBackgroundTestFunction)が終了すると、await句の次の行から処理が再開されます(緑の線)。このときstring resultには、バックグランド処理(MyBackgroundTestFunction)のreturn句で返された情報が入ります。これは必ずしもstring型である必要はなく、自分の好きデータ型を指定できますので、処理後の情報を渡すときなどに使えます。

動作確認

では、先程のサンプルプログラムの様々なポイントにログを挿入して、実行結果を見てみたいと思います。

 

実行結果

  1. まずRoute(0)では、フォームがロードされて、フォームロードイベントが発生し、FormTest_Load 関数が呼ばれました。
  2. FormTest_Load関数では、async関数の「MyTestFunction」が呼び出され、Route(2)のログが出力されています。ここでバックグランド処理が起動されています。
  3. Route(3)では、await句が呼び出されます。そのため、呼び出し元の処理はReturnされ、MyTestFunction関数から上位の関数FormTest_Loadへ戻ったために、ここでRoute(1)のログが出ています。
  4. バックグランド処理側(ThreadId(3)側)では、処理中のログの出力をするとともに、プログレス報告が呼び出し元に報告されています。
  5. Route(4)のログは、バックグランド処理の結果情報を受け取って表示しています。
このカテゴリのページリンク