もくじ
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f
やりたいこと
Taskで非同期処理をしているときに、Taskを.Wait()
する処理があった。
そこで例外が発生したときに、そいつをtry catchすると、起きていたはずの例外ではなくAggregateException
という例外がスローされていた。
元々タスクの中では別の例外が起きていたはずなのに、そいつはどこに行ったのか?
実際に起きた例外をキャッチするにはどうしたらよいのか?調べたい。
やったこと
いろいろ調べたところ、
どうやら待ち方によって例外の受け取り方を変えないといけない様子。
今回調べた限りでは、下記のような待ち方ごとの例外のキャッチの仕方があった。
①awaitで待つときのパターン
awaitするときは、そこをtry catchで囲むだけで、通常通りに例外がcatchできる。
// awaitしたTaskの例外privateasyncvoidButton_Click(objectsender,RoutedEventArgse){try{awaitTask.Run(()=>{thrownewNotImplementedException();});}catch(Exceptionex){Debug.WriteLine("1:"+ex.GetType());}}
②Wait()で待つときのパターン
Wait()したときは、例外がAggregateException
に包まれて上がってくる。
実際に何の例外が起きたかは、AggregateException
のInnerException
プロパティを見る必要がある。
// Wait()したTaskの例外privatevoidButton_Click_1(objectsender,RoutedEventArgse){try{Task.Run(()=>{thrownewNotImplementedException();}).Wait();}catch(Exceptionex){Debug.WriteLine("a:"+ex.GetType());if(exisAggregateExceptionage){Debug.WriteLine("b:"+age.InnerException.GetType());}}}
③待たないパターン
待たないタスクの場合は、普通にtry catchで囲んでも例外は取れない。
(その場で処理してないのだから当たり前かも)
その場合は、終わったタスクの変数のException
プロパティを見て、何の例外が起きてタスクが終わったのかを調べる。
実験では、例外を調べたいタスクのContinueWith()
メソッドを使って、タスクが終わった時に行う処理を登録して、その中でException
プロパティを見た。
またその時も、Wait()
のときと同じようにAggregateException
に包まれて上がってきてるので、実際に何の例外が起きたかはAggregateException
のInnerException
プロパティを見る。
// 待たないTaskの例外privatevoidButton_Click_2(objectsender,RoutedEventArgse){try{vart=Task.Run(()=>{thrownewNotImplementedException();});t.ContinueWith((compt)=>{Debug.WriteLine("A:"+compt.Exception.GetType());if(compt.ExceptionisAggregateExceptionage){Debug.WriteLine("C:"+age.InnerException.GetType());}});}catch(Exceptionex){// ここには来ないDebug.WriteLine("B:"+ex.GetType());}}
[番外編] 複数TaskをTask.WhenAll()で待った時に各タスクの中で起きた例外をまとめて取る
複数のタスクをTask.WhenAll()で待ったときに、それぞれのタスクで例外が起きていた時にそれを纏めて取ることができる。
ただ直感的には取れず、少々小細工必要。
Task.WhenAll()をtry catchでキャッチした例外は、複数例外がまとめられたAggregateException
ではなく、各タスクで起きた例外のうちの1つだけが入ったものになっている。
起きた例外全部を拾おうとすると、Task.WhenAll()のタスクを受けたローカル変数の中のException
プロパティを見る必要がある。(それがAggregateException
になっている)
privateasyncvoidButton_Click_4(objectsender,RoutedEventArgse){vart1=Task.Run(()=>{thrownewNotImplementedException();});vart2=Task.Run(()=>{thrownewArgumentException();});vart3=Task.Run(()=>{thrownewInvalidOperationException();});varall=Task.WhenAll(t1,t2,t3);try{// WhenAllのタスクのローカル変数を作って、それをtry catchするawaitall;}catch(Exceptionex){// ex には例外のうちの1つしか入ってないので、// WhenAllのタスクのローカル変数のExceptionプロパティ// (それがAggregateExceptionになってる)を見て// すべての例外を取り出すif(all.ExceptionisAggregateExceptionage){age.InnerExceptions.ToList().ForEach((ages)=>Debug.WriteLine(ages.GetType()));}}}
上記のage.InnerExceptions.ToList().ForEach((ages) => Debug.WriteLine(ages.GetType()));
の部分では、例外AggregateException
を完全に握りつぶしている(再throwしない)が、AggregateException
のHandle
メソッドを使っても似たようなことが書ける。
その場合、Handleでtrueを返すようにすると、例外はそこで処理済みとして、再throwしないようにもできる。(falseだと再throwする)
privateasyncvoidButton_Click_4(objectsender,RoutedEventArgse){vart1=Task.Run(()=>{thrownewNotImplementedException();});vart2=Task.Run(()=>{thrownewArgumentException();});vart3=Task.Run(()=>{thrownewInvalidOperationException();});varall=Task.WhenAll(t1,t2,t3);try{awaitall;}catch(Exceptionex){if(all.ExceptionisAggregateExceptionage){age.Handle((excep)=>{// excep はAggregateExceptionに包まれている個別の例外。Debug.WriteLine(excep.GetType());// trueにしたら、ここでもう処理済みということで例外を再throwしない// falseにしたら、まだ未処理ということで例外を再throwする。returntrue;});}}}
参考
■MS公式 Async・Await 非同期プログラミングのベスト プラクティス
Async・Awaitの使い方から例外の処理の仕方まで、細かく書いてくれてる。
@SSSSYYYYさんのコメントでこのページを知りました。ありがとうございます!
https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
■async/awaitで例外処理をするには?
https://www.atmarkit.co.jp/ait/articles/1805/16/news018.html
■【C# Tips】非同期処理(Task)の例外処理を極めて、障害を正しく検知しよう!!
https://www.sukerou.com/2018/09/task.html
■async/awaitによる非同期処理の例外の謎
いろいろ試行錯誤されてた様子。なにげにデバッグ実行したときに、一旦例外スロー部分でとまるけど続けてF5おしたら動かせる、という部分がへぇーとなった。
https://qiita.com/habu1010/items/08177698fa3826474c0b
■MS公式 AggregateException
https://docs.microsoft.com/ja-jp/dotnet/api/system.aggregateexception?view=net-5.0
※公式サンプルでは、AggregateException の Handle
メソッドを使ってAggregateException の中の複数の例外を処理しているみたい。