C# もいろいろ進化して、最近ではBackgroundWorkerの代わりにTaskというものが登場してきていますね。これは、BackgroundWorkerよりもコーディング量が少なくて同じようなことが実現できるので便利なのですが、巷の説明ブログを読んでいると、なんだか間違った解説をもっともらしく発表している残念なサイトが多く、実際の使用を想定したサンプルも少ないような気がしたので、ここでサンプルとともにまとめておきたいと思います。
サンプルプログラム(スケルトン)
まずは、プログラム全体を見られるようにスケルトンの状態でソースを公開します。このプログラムは、Windowsのあるフォームのソースです。フォームロードイベント(FormTest_Load)が起点となり、フォームが画面に表示される際に、サーバなどからデータを取ってきて画面に表示するようなことを想定しています。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TestTaskProgram { public partial class FormTest : Form { public FormTest() { InitializeComponent(); } private void FormTest_Load(object sender, EventArgs e) { MyTestFunction(); } private async void MyTestFunction() { Progress<string> progress = new Progress<string>(onProgressChanged); Task<string> task = Task.Run<string>(() => MyBackgroundTestFunction(progress)); string result = await task; Console.WriteLine( result ) ; } private string MyBackgroundTestFunction(IProgress<string> progress) { Thread.Sleep(5000); return DateTime.Now.ToString(); } private void onProgressChanged(string msg) { Console.WriteLine(msg); } } }
プログラムの流れ
プログラムの流れを見ていくと、以下のようになります。
- フォームロードイベントが発行されFormTest_Load関数が呼び出されます。
- FormTest_Load関数の中ですぐに時間がかかる処理(バックグランド処理)を呼び出す役目の関数(MyTestFunction)が呼び出されます。
- 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句の動作は以下のようになります。

まず、Task.Runによってバックグランドプロセスが起動されます(青の線)。
次に、呼び出し元のほうでは、await句によってReturnと同じ動作をし、MyTestFunction関数から抜け、さらにFormTest_Loadからも抜けて、メッセージループへ戻ります(赤い線)。
バックグランドの処理(MyBackgroundTestFunction)が終了すると、await句の次の行から処理が再開されます(緑の線)。このときstring resultには、バックグランド処理(MyBackgroundTestFunction)のreturn句で返された情報が入ります。これは必ずしもstring型である必要はなく、自分の好きデータ型を指定できますので、処理後の情報を渡すときなどに使えます。
動作確認
では、先程のサンプルプログラムの様々なポイントにログを挿入して、実行結果を見てみたいと思います。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TestTaskProgram { public partial class FormTest : Form { public FormTest() { InitializeComponent(); } private void FormTest_Load(object sender, EventArgs e) { Console.WriteLine(String.Format("Route(0):ThreadId({0}): FormTest_Load Start", Thread.CurrentThread.ManagedThreadId)); MyTestFunction(); Console.WriteLine(String.Format("Route(1):ThreadId({0}): FormTest_Load End", Thread.CurrentThread.ManagedThreadId)); } private async void MyTestFunction() { Progress<string> progress = new Progress<string>(onProgressChanged); Console.WriteLine(String.Format("Route(2):ThreadId({0}): MyTestFunction Task.Run Start", Thread.CurrentThread.ManagedThreadId)); Task<string> task = Task.Run<string>(() => MyBackgroundTestFunction(progress)); Console.WriteLine(String.Format("Route(3):ThreadId({0}): MyTestFunction await task Start", Thread.CurrentThread.ManagedThreadId)); string result = await task; Console.WriteLine(String.Format("Route(4):ThreadId({0}): MyTestFunction await task Ended. Result={1}", Thread.CurrentThread.ManagedThreadId, result)); } private string MyBackgroundTestFunction(IProgress<string> progress) { Console.WriteLine(String.Format("Route(5):ThreadId({0}): MyBackgroundTestFunction Start", Thread.CurrentThread.ManagedThreadId)); progress.Report("Progress; START"); Thread.Sleep(5000); progress.Report("Progress: END"); Console.WriteLine(String.Format("Route(6):ThreadId({0}): MyBackgroundTestFunction Ended", Thread.CurrentThread.ManagedThreadId)); return DateTime.Now.ToString(); } private void onProgressChanged(string msg) { Console.WriteLine(msg); } } }
実行結果
Route(0):ThreadId(1): FormTest_Load Start Route(2):ThreadId(1): MyTestFunction Task.Run Start Route(3):ThreadId(1): MyTestFunction await task Start Route(1):ThreadId(1): FormTest_Load End Route(5):ThreadId(3): MyBackgroundTestFunction Start Progress; START Route(6):ThreadId(3): MyBackgroundTestFunction Ended Progress: END Route(4):ThreadId(1): MyTestFunction await task Ended. Result=2019/10/19 15:40:48
- まずRoute(0)では、フォームがロードされて、フォームロードイベントが発生し、FormTest_Load 関数が呼ばれました。
- FormTest_Load関数では、async関数の「MyTestFunction」が呼び出され、Route(2)のログが出力されています。ここでバックグランド処理が起動されています。
- Route(3)では、await句が呼び出されます。そのため、呼び出し元の処理はReturnされ、MyTestFunction関数から上位の関数FormTest_Loadへ戻ったために、ここでRoute(1)のログが出ています。
- バックグランド処理側(ThreadId(3)側)では、処理中のログの出力をするとともに、プログレス報告が呼び出し元に報告されています。
- Route(4)のログは、バックグランド処理の結果情報を受け取って表示しています。
このカテゴリのページリンク |
同一カテゴリの記事一覧
- 【テクニカル】WordPressを多言語化する
- 【テクニカル】CentOs8 インストール後にすべきことメモ
- 【テクニカル】一番簡単なCentOS PHP7.3から7.4への移行方法
- 【テクニカル】パソコンへのLinuxの導入
- 【テクニカル】Open Graph 手書きのウエブサイトをSNSでシェアできるよう…
- 【テクニカル】C# NotifyIconの実装
- 【テクニカル】一番わかりやすいBracketsの導入とトラブルシューティング
- 【テクニカル】無料SSL Let's EncryptでWordpressサイトをSSL化する
- 【テクニカル,未分類】【Windows10】ホテルのWiFi接続時に「インターネット接続…
- 【テクニカル】Let's Encryptのトラブルシューティング
- 【テクニカル】C# で Task を使う
- 【テクニカル】MacBookProにBootcampでWindows10を入れる際のポイント
- 【テクニカル】PHPMailer導入とトラブル対策
- 【テクニカル】PHPMailerのログをSYSLOGに出力
- 【テクニカル】Visual Studio 2017で使うSSHの秘密鍵をputtygenで作成す…
- 【テクニカル】Wordpressの投稿一覧で要らない項目を削除する方法
- 【テクニカル】Google SEO の要点 - Better Google SEO
- 【テクニカル】wordpressを始める前に絶対すべき準備 - Things you need …
- 【テクニカル】Wordpress URL設定間違い・サイト移動のトラブル対応 - Ho…
- 【テクニカル】VisualStudio2013セットアッププロジェクトを戻す! - Vis…
- 【テクニカル】C# でWindowsサービスの「説明」の文を取得する方法 - C# …
- 【テクニカル】動画配信に最低限必要なモノ(動画配信環境)
- 【テクニカル】動画配信のトラブル対処(ブーンというノイズが乗る)
- 【テクニカル】client denied by server configuration: