web-dev-qa-db-ja.com

タスク内で例外をスローする-「await」とWait()

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キーワードを使用した場合、同じことは起こりません。
その振る舞いの説明は何ですか?

20
Yaron Levi

目標は、同期バージョンのように見える/動作させることです。 Jon Skeetは、彼のEduasyncシリーズ、特にこの投稿でこれを説明する素晴らしい仕事をしています。

http://codeblog.jonskeet.uk/2011/06/22/eduasync-part-11-more-sophisticated-but-lossy-exception-handling/

9
James Manning

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

2
Oleg

これは、Stephen Toubによる、Task.Wait()とawaitの例外タイプに違いがある理由の詳細な説明です。

。NET 4.5でのタスク例外処理

.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つの悪のうちの重要な少ないものと見なされています。

0
Vadim S.