ホームに戻る
出典 :
関連 :
目次 :
並列処理における例外捕捉
並列処理において、タスクで発生した例外は、待ち方によって捕捉する方法が異なる。
await で待つ場合
Task の終了を await で待つ場合は、通常の手法で例外を捕捉できる。
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// 例外を送出する Task を開始して await
await Task.Run( () => { throw new NotImplementedException(); } );
}
// 例外は Exception として捕捉可能
catch (Exception ex)
{
Debug.WriteLine( ex.Message );
}
}
注意が必要な点
上記のように async 修飾されたメソッド中に try - catch 節を置き、try 節中で await を行う場合は捕捉が可能だが、
async 修飾されたメソッドを呼び元で監視する場合は例外を捕捉できない。
これは呼び元のスレッドと、Task の完了を待機するスレッドが異なることによる。
// ボタンクリック時の処理(イベントハンドラ)
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// 実処理
Button_Click_Core();
}
// 例外を捕捉しようとしているが…?
// ⇒ 実際は捕捉できない
catch (Exception ex)
{
Debug.WriteLine( ex.Message );
}
}
// 実処理( async )
private async Task Button_Click_Core()
{
// 例外を送出する Task を開始して await
await Task.Run( () => { throw new NotImplementedException(); } );
}
Wait() で待つ場合
Task の終了を Wait() / WaitAll() / WaitAny() で待つ場合、例外は AggregateException として捕捉される。
例外の本体は AggregateException.InnerException から参照できる。
// Wait()したTaskの例外
private void Button_Click_1(object sender, RoutedEventArgs e)
{
try
{
Task.Run(() =>
{
throw new NotImplementedException();
}).Wait();
}
// ここで例外は捕捉できるが、例外の中身は InnerException を参照する必要がある
catch (Exception ex)
{
Debug.WriteLine("a:" + ex.GetType());
if (ex is AggregateException age)
{
Debug.WriteLine("b:" + age.InnerException.GetType());
}
}
}
待たない場合
Task の終了を待たない場合、開始元では例外は捕捉できない。
例外の内容を確認する場合は、Task 変数の Exception プロパティを参照すればよい。
この場合、前節と同様に例外は AggregateException として格納されているため、InnerException プロパティを参照する必要がある。
// 待たないTaskの例外
private void Button_Click_2(object sender, RoutedEventArgs e)
{
try
{
// Task を開始するのみで終了を待たない
var t = Task.Run(() =>
{
throw new NotImplementedException();
});
// t が終了した後に行う処理をコールバックとして登録する
t.ContinueWith((compt) =>
{
Debug.WriteLine("A:" + compt.Exception.GetType());
if (compt.Exception is AggregateException age)
{
Debug.WriteLine("C:" + age.InnerException.GetType());
}
});
}
// t で例外が発生しても、ここでは捕捉できない
catch (Exception ex)
{
:
}
}