ホームに戻る
出典 :
関連 :
目次 :
.NETにおける並列処理
.NETでは並列処理(マルチタスク)がサポートされている。
過去(.NET Framework 1 ~ 3) Thread 、ThreadPool 、BackgroundWorker といったクラスを用いて並列処理を実現していたが、これらは扱いが煩雑で習得難度も高かった。
.NET Framework 4で Task クラスが登場し、比較的容易に並列処理を実現できるようになった。
本記事ではこの Task を用いることを前提として解説を行う。
タスクの開始と完了待ち
Task.Run()
Task task = Task.Run( () => HeavyWork(2000000000) );
Task.Run() は引数で指定された処理を別スレッドで開始する。
引数は
デリゲート( Action (戻り値なしのメソッド)または Func (戻り値のあるメソッド) )で、デルタ式も使用できる。
スレッドの終了待ちや実行結果(戻り値)の取得は戻り値の
Task<T> に対して行う。
ここで T は Task.Run() に渡されるメソッド(ここでは HeavyWork() )の戻り値の型である。戻り値が存在しない( void )場合は単に Task となる。
また Task.Run() は指定された処理をスレッドプールに投入するのみで、通常、その処理をどのスレッドで実行するのかは問わない。
async / await
非同期処理の完了を待つ。後述の Wait() と異なり、スレッドをブロックしない点が特徴。
- await : タスクの終了を待機する。その際に呼び元に制御を戻す。
- async : そのメソッド中で await を使用する。
// メソッド内で await を用いるため async 修飾
// 本来の戻り値は void だが、async 修飾されているため Task に変更する
private async Task ExecuteAsync()
{
// (1)
text += "計算開始" + Environment.NewLine;
// (2) 重い処理を別タスクで実行開始(Task.Run())
// await 修飾されているため、次行以降は別タスクの完了後に実行される
double result = await Task.Run(() => HeavyWork(2000000000));
// (3)
text += result.ToString() + Environment.NewLine;
}
上記は async / await を用いた非同期処理の例である。シーケンス図中の塗りつぶし(薄黄、薄青)はスレッドを示す。
await キーワードに到達すると後続のタスクを別スレッドで開始するとともに、呼び元に制御を戻すことが重要な点としてあげられる。
時間のかかる処理を行う場合でも呼び元をブロックしないため、特に呼び元がUIスレッドの場合に有効である。
(UIスレッドがブロックされるとユーザ操作を受け付けることができなくなる。)
また、await はタスクの終了を待機するため、後続行(コード中の (3) )は、待機したタスクの終了後に実行される。
このように、比較的少ない記述で非同期処理を実現することができる。
ここで、メソッド(ここでは ExecuteAsync() )中で await を用いる場合、メソッドを async 修飾する必要があるとともに、
戻り値を Task とする必要がある。
- 元々の戻り値が無い( void ) : Task ( void ⇒ async Task)
- 元々の戻り値が T 型 : Task<T> ( T ⇒ async Task<T>)
ただし、(UI)イベントハンドラは例外的に async void を用いることができる。
上記の例でもわかるように、async 修飾を行った場合でも明示的に Task を返す必要はない。
応用
HttpClient hc = new HttpClient();
:
private async void button_Click(object sender, RoutedEventArgs e)
{
// ダウンロードタスクを別スレッドで実行
// ( Task オブジェクトに格納)
Task<string> gethtmltask = hc.GetStringAsync("http://www.example.jp/kussoosoi.php");
// この行は非同期タスクの終了を待たずに実行される
MessageBox.Show("HTMLを取得しています");
// ダウンロード開始
// 非同期タスクの完了を待機し、結果を取得する
textBox.Text = await gethtmltask;
}
開始した非同期タスクを変数に格納しておき、あとで await する手法。
メッセージボックス表示の裏でHTTP通信を行うので、体感的な待ち時間の短縮が期待できる。
Wait() / Result / WaitAll() / WaitAny()
HttpClient hc = new HttpClient();
// 非同期実行開始
Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/");
Task<string> t2 = hc.GetStringAsync("https://www.bing.com/");
// t1 が終わるまでスレッドをブロックする
t1.Wait();
// t2 が終わるまでスレッドをブロックし、結果を取得する
string binghtml = t2.Result;
// いずれかの Task が終わるまでスレッドをブロックする
// 0 : t1 が先に終了
// 1 : t2 が先に終了
int completedTaskIndex = Task.WaitAny(t1, t2);
// いずれかの Task が終わるまでスレッドをブロックする(タイムアウト付き)
// 0 : t1 が先に終了
// 1 : t2 が先に終了
// -1 : いずれも終了しないまま50ms経過
int completedTaskIndex2 = Task.WaitAny(new[] { t1, t2 }, 50);
// すべての Task が終わるまでスレッドをブロックする
Task.WaitAll(t1, t2);
// すべての Task が終わるまでスレッドをブロックする(タイムアウト付き)
// true : いずれも50ms以内に終了
// false : いずれかが終了しないまま50ms経過
bool allTasksCompleted = Task.WaitAll(new[] { t1, t2 }, 50);
- Wait() : 実行中の一つのタスクの終了を待機する。
- Result : タスクの実行結果を取得する。タスクが終了していない場合は終了を待機する。
- WaitAny() : 引数に指定されたタスクのうち、いずれかが終了するまで待機する。
- WaitAll() : 引数に指定されたタスクすべてが終了するまで待機する。
await と異なり、呼び元に制御を戻さない。
WhenAll() / WhenAny()
タスクの終了通知を受け取る。複数のタスクを await する際に用いる。戻り値が異なる点に注意。
HttpClient hc = new HttpClient();
Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/");
Task<string> t2 = hc.GetStringAsync("https://www.bing.com/");
// スレッドをブロックせずにすべてのタスクの終了を待機
string[] htmls = await Task.WhenAll(t1, t2);
// -> htmls[0] : t1 の実行結果
// htmls[1] : t2 の実行結果
// スレッドをブロックせずにいずれかのタスクの終了を待機
Task<string> completedTask = await Task.WhenAny(t1, t2);
IsCompleted / IsCompletedSuccessfully / IsFaulted / IsCanceled
タスクが終了しているかを示すプロパティ。
- IsCompleted : タスクが終了していれば true 、そうでなければ false (終了要因不問)。
- IsCompletedSuccessfully : タスクが正常終了していれば true 、そうでなければ false 。
- IsFaulted : タスクが不正終了していれば true 、そうでなければ false 。
- IsCanceled : タスクが中断(キャンセル)されていれば true 、そうでなければ false 。
HttpClient hc = new HttpClient();
Task<string> task = hc.GetStringAsync("https://www.microsoft.com/");
// タスクが完了していなければメッセージ表示
if (!task.IsCompleted)
{
Console.WriteLine("ちょっとまってね");
}
// タスクの完了を待機
var html = await task;
Console.WriteLine(html);
例外の捕捉
並列処理においては、例外の捕捉には制約がある。詳細は
リンク先を参照。