static async void Main(string[] args)
{
Task t = new Task(() => { throw new Exception(); });
try
{
t.Start();
t.Wait();
}
catch (AggregateException e)
{
// When waiting on the task, an AggregateException is thrown.
}
try
{
t.Start();
await t;
}
catch (Exception e)
{
// When awating on the task, the exception itself is thrown.
// in this case a regular Exception.
}
}
TPLでは、タスク内で例外をスローすると、AggregateExceptionでラップされます。
しかし、awaitキーワードを使用した場合、同じことは起こりません。
その振る舞いの説明は何ですか?
目標は、同期バージョンのように見える/動作させることです。 Jon Skeetは、彼のEduasyncシリーズ、特にこの投稿でこれを説明する素晴らしい仕事をしています。
TPLではAggregateException
が使用されます。これは、待機操作で複数のタスクを使用できるため(タスクには子タスクをアタッチできる)、それらの多くが例外をスローする可能性があるためです。ここで子タスクの例外セクションを見てください:
https://msdn.Microsoft.com/ru-ru/library/dd997417(v = vs.110).aspx
await
には、常に1つのタスクしかありません。
参照 https://msdn.Microsoft.com/ru-ru/library/dd997415(v = vs.110).aspx
これは、Stephen Toubによる、Task.Wait()とawaitの例外タイプに違いがある理由の詳細な説明です。
.NET 4でTask.Waitを設計するときは、常に集計を伝播することを選択しました。この決定は、詳細を上書きしない必要性だけでなく、複数の例外の可能性が非常に一般的である、当時のタスクの主なユースケースであるフォーク/結合並列処理のユースケースにも影響されました。
Task.Waitの高レベルに似ていますが(つまり、タスクが完了するまで前進は行われません)、「タスクを待つ」は非常に異なる主要なシナリオのセットを表します。フォーク/結合の並列処理に使用されるのではなく、「await task」の最も一般的な使用法は、シーケンシャルな同期コードを取得し、それをシーケンシャル非同期コードに変換することです。同期操作を実行するコード内の場所では、タスクで表される非同期操作に置き換えて「待機」します。そのため、フォーク/ジョイン操作の待機(Task.WhenAllの利用など)を確実に使用できますが、80%の場合ではありません。さらに、.NET 4.5ではSystem.Runtime.ExceptionServices.ExceptionDispatchInfoが導入されました。これにより、スタックトレースやワトソンバケットなどの例外の詳細を失うことなく、スレッド間で例外をマーシャリングできるという問題が解決されます。例外オブジェクトを指定すると、それをExceptionDispatchInfo.Createに渡します。このオブジェクトは、Exceptionオブジェクトへの参照とその詳細のコピーを含むExceptionDispatchInfoオブジェクトを返します。例外をスローするときは、ExceptionDispatchInfoのThrowメソッドを使用して、例外の内容を復元し、元の情報を失うことなくスローします(現在のコールスタック情報は、すでにExceptionに格納されているものに追加されます)。
そのため、常に最初にスローするか、常にアグリゲートをスローするかを選択できるため、「待機」では常に最初にスローすることを選択します。ただし、これは、同じ詳細にアクセスできないという意味ではありません。いずれの場合も、タスクのExceptionプロパティは、すべての例外を含むAggregateExceptionを返すため、スローされたものをキャッチして、必要に応じてTask.Exceptionを参照することができます。はい、これにより、「task.Wait()」と「await task」を切り替える際の例外動作に不一致が生じますが、これは2つの悪のうちの重要な少ないものと見なされています。