12

失敗したタスク(例外が設定されているタスク)を待っているときawait格納された例外を再スローします。格納された例外がAggregateExceptionそれは最初のものを再投げそして残りを捨てるでしょう。

どうやって使えますかawaitそして同時にオリジナルを投げるAggregateException誤ってエラー情報を紛失しないように

これに対するハックな解決策を考えることはもちろん可能であることに注意してください。awaitその後、電話Task.Wait)私は本当にきれいな解決策を見つけたいです。ここでのベストプラクティスは何ですか?

カスタムのウェイターを使うことを考えましたが、内蔵のTaskAwaiter完全に再現する方法がわからないという魔法がたくさん含まれています。 TPL型の内部APIを呼び出します。私もしません欲しいですそのすべてを再現するために。

あなたがそれと遊びたいならば、これは短いreproです:

static void Main()
{
    Run().Wait();
}

static async Task Run()
{
    Task[] tasks = new[] { CreateTask("ex1"), CreateTask("ex2") };
    await Task.WhenAll(tasks);
}

static Task CreateTask(string message)
{
    return Task.Factory.StartNew(() => { throw new Exception(message); });
}

2つの例外のうち1つだけがスローされますRun

スタックオーバーフローに関する他の質問はこの特定の問題に対処していないことに注意してください。重複を提案するときは注意してください。


  • このブログ記事で、彼らがなぜ例外処理の実装を選択したのかを説明しましたか?await彼らがしたように?blogs.msdn.com/b/pfxteam/archive/2011/09/28/10217876.aspx - Matthew Watson
  • はい、ありがとう。私はそのような一般的な事件の推論に同意しますが、私のユースケースはカバーされていません。 - usr
  • "すべての場合において、TaskのExceptionプロパティは依然としてすべての例外を含むAggregateExceptionを返すので、どちらをスローしてもキャッチして、必要に応じてTask.Exceptionを調べることができます。 - Timbo

4 답변


20

私はあなたの質問の題名にその意味があることに同意しません。awaitの行動は望ましくない。大多数のシナリオでは意味があります。でWhenAll状況、どのくらいの頻度であなたは本当に知っておく必要がありますすべて1つだけではなく、エラーの詳細について

主な難しさAggregateException例外処理です。つまり、特定の型を捕まえることができなくなります。

とは言っても、拡張メソッドを使用すると、希望する動作を簡単に取得できます。

public static async Task WithAggregateException(this Task source)
{
    try
    {
        await source.ConfigureAwait(false);
    }
    catch
    {
        throw source.Exception;
    }
}


  • すべての例外を取得することは診断にとって重要です。 nullrefなどを見逃すことは決してありません。それらは記録されなければなりません。取り扱い例外は別のシナリオです。ところで、解決策はうまくいきます。ありがとう。 - usr
  • あなたが話しているのなら致命的または骨頭の例外、トップレベルのハンドラを使用することをお勧めします。UnobservedTaskException(アプリケーションハンドラ)の代わりに繰り返されるtry/catchブロック - Stephen Cleary
  • 私はほとんど同意します。私が念頭に置いているシナリオは確かにトップレベルのエラーハンドラです(それがするすべてはログクラッシュです)。しかし、完全なクラッシュ情報がどういうわけかそこに到着しなければなりません。 UnobservedTaskExceptionとアプリケーションハンドラは、エラーが観察されたが飲み込まれたりする可能性があるため、awaitが存在しても待機することを保証しません。エラーが飲み込まれるのを見るととても緊張します。私は生産が月のための重大なエラーを被り、誰も気付いていないケースをあまりにも多く見ました。 - usr
  • UnobservedTaskExceptionまだ育っている監視されていない例外の場合(プロセスをクラッシュさせることはもうありません)。観察された、飲み込まれた例外がある唯一のケースはWhenAllそして、それはあなたが複数の例外を持っているときにだけ起こりますではない飲み込んだ。あなたは本当にする必要がありますやってみるで例外を無視するawait。 - Stephen Cleary
  • @BatteryBackupUnit:タスクには2つの主な用途があります。非同期コード用、および「作業単位」としてのその他並列コード用。私はあなたのアプローチは変更可能/凍結可能を必要とするだろうと思いますAggregateException少なくとも。お気軽にあなたのアイデアを話し合いのために投稿してください。余談:技術的には、await展開しないでくださいAggregateExceptionしかし、これは問題ありません。 - Stephen Cleary

3

私は遅れていることを知っていますが、私はあなたが望むことをするこのきちんとした小さなトリックを見つけました。待機中のタスクでは例外のフルセットが利用可能であるため、このタスクの待機または.Resultを呼び出すと、集計例外がスローされます。

    static void Main(string[] args)
    {
        var task = Run();
        task.Wait();
    }
    public static async Task Run()
    {

        Task[] tasks = new[] { CreateTask("ex1"), CreateTask("ex2") };
        var compositeTask = Task.WhenAll(tasks);
        try
        {
            await compositeTask.ContinueWith((antecedant) => { }, TaskContinuationOptions.ExecuteSynchronously);
            compositeTask.Wait();
        }
        catch (AggregateException aex)
        {
            foreach (var ex in aex.InnerExceptions)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

    static Task CreateTask(string message)
    {
        return Task.Factory.StartNew(() => { throw new Exception(message); });
    }


3

例外処理(タスク並列ライブラリ)

私はもっと言うことができますが、それはただパディングになるでしょう。それと遊ぶ、それは彼らが言うように動作します。あなたはただ気をつけなければなりません。

多分あなたはこれが欲しい

神(Jon Skeet)は例外処理待ちを説明します

(個人的には待たずに恥ずかしがり屋ですが、それが私の好みです

コメントに応えて(コメント返信には長すぎる)

それからここでのためのものの源があるベストプラクティスとして類似した議論のためのあなたの出発点としてスレッドを使用してください。

渡すためのコードを実装しない限り、例外は喜んで飲み込まれます(例えば、待機がラップしていると思われる非同期パターン...イベントを発生させるときにそれらをevent argsオブジェクトに追加する)。任意の数のスレッドを起動して実行するシナリオでは、順序や各スレッドを終了する時点を制御することはできません。さらに、あるパターンのエラーが別のパターンに関連している場合は、このパターンを使用しないでください。そのため、残りの部分の実行は完全に独立していることを強く示唆しています。強くこれらのスレッドの例外はすでに例外として扱われていることを意味します。これらのスレッドで発生したスレッドで例外を処理すること(奇妙なこと)を超えて何かをしたい場合は、参照によって渡されるロックコレクションにそれらを追加する必要があります。情報のコンカレントバッグを使用して、コンテキストを識別するために必要な情報に例外をラップします。

ユースケースを混同しないでください。


  • 最初のリンクは扱っていませんawait。 2番目のものは有効な答えを含んでいます。ありがとう。私は私が発見したいので私は数日の間他の答えを入らせますベスト溶液。 - usr
  • 問題は私があなたのユースケースに同意しないということです。あなたがawaitを使っているとき、あなたはそれぞれのタスクがそれ自身の例外を処理すると仮定していると思います。または少なくとも私はそれがそのパターンで想定されていると思います。タスクの外で例外を処理したい場合は、一番上のパターンを使用してください。私はおそらくそれを説明するべきです。あなたが総合的な例外 - 質問 - がほしいと思うならまた、最初はあなたの選択であるべきです。 - John Nicholas
  • 一番上のパターンはブロッキングを含み、これはアドレスを待つのとは全く異なるユースケースです。有効なユースケースですが私のものではありません - ここでは非同期IOとノンブロッキングが必要です。また、タスクは必ずしも自分自身のエラーを処理するわけではありません。その場合、エラーの伝播は必要ありません。エラーは想定された通常の制御の流れが中断されるように伝播します。欲しいのはただすべて任意のエラーではなく、エラー。 - usr
  • 更新された... ofcタスクはそれ自身のエラーを処理します...タスクそれは作業単位の周りのカプセル化の単位です。私が主張するであろう例外を処理する仕事はその決定的な部分であるか、そうでなければ100個のスレッドがすべて例外を投げて1つのコードでブロックするのであなた自身のためにロッキングナイトメアを作成しています。それはとにかく本質的にタスク自体の中にあることを理解するでしょう...あるいは私と妥協して例外を処理するためにラムダを渡します。 - John Nicholas
  • @ JohnNicholasによる神の記事への参照が壊れています( - Konstantin Chernov

0

期待した例外だけを捉えるために練習をあきらめたくはありません。これにより、次のような拡張方法が得られます。

public static async Task NoSwallow<TException>(this Task task) where TException : Exception {
    try {
        await task;
    } catch (TException) {
        var unexpectedEx = task.Exception
                               .Flatten()
                               .InnerExceptions
                               .FirstOrDefault(ex => !(ex is TException));
        if (unexpectedEx != null) {
            throw new NotImplementedException(null, unexpectedEx);
        } else {
            throw task.Exception;
        }
    }
}

消費するコードは次のようになります。

try {
    await Task.WhenAll(tasks).NoSwallow<MyException>();
catch (AggregateException ex) {
    HandleExceptions(ex);
}

骨頭の例外は、例外が同時に発生した場合でも、同期の世界と同じ効果があります。MyException偶然に。とのラッピングNotIplementedException元のスタックトレースを失わないようにするのに役立ちます。

リンクされた質問


関連する質問

最近の質問