17

###はじめに

しばらくコードに戸惑った後、例外が必ずしもを介して伝播するとは限らないことを発見しましたContinueWith

int zeroOrOne = 1;
Task.Factory.StartNew(() => 3 / zeroOrOne)
    .ContinueWith(t => t.Result * 2)
    .ContinueWith(t => Console.WriteLine(t.Result))
    .ContinueWith(_ => SetBusy(false))
    .LogExceptions();

この例では、行が例外のチェーンを「リセット」するため、ゼロ除算の例外は見られず、その後、 「タスクの例外が観察されませんでした...」というメッセージSetBusyが表示されて爆発します。

だから...私は自分で小さな拡張メソッドを書きました(さまざまなオーバーロードがたくさんありますが、基本的にはすべてこれを行っています):

public static Task ContinueWithEx(this Task task, Action<Task> continuation)
{
     return task.ContinueWith(t =>
     {
         if(t.IsFaulted) throw t.Exception;
         continuation(t);
     });
}

もう少し調べてみると、このブログ投稿に出くわしました。彼は同様のソリューションを提案していますが、TaskCompletionSource を使用しています。これは (言い換えると) 次のようになります。

public static Task ContinueWithEx(this Task task, Action<Task> continuation)
{
     var tcs = new TaskCompletionSource<object>();
     task.ContinueWith(t =>
     {
         if(t.IsFaulted) tcs.TrySetException(t.Exception);
         continuation(t);
         tcs.TrySetResult(default(object));
     });
     return tcs.Task;
}

###質問 これら 2 つのバージョンは厳密に同等ですか? それとも、 と の間に微妙な違いがthrow t.Exceptionありtcs.TrySetException(t.Exception)ますか?

また、これを行った人がインターネット全体で明らかに 1 人しかいないという事実は、私がこれを行う慣用的な方法を見逃していることを示していますか?

4

1 に答える 1

12

2つの違いは微妙です。最初の例では、タスクから返された例外をスローしています。これにより、CLRでの通常の例外のスローとキャッチがトリガーされ、ContinueWithキャッチとラップが行われ、チェーン内の次のタスクに渡されます。

2番目の呼び出しTrySetExceptionでは、例外をラップしてチェーン内の次のタスクに渡しますが、try/catchロジックはトリガーされません。

1つ後の最終結果ContinueWithExAggregateException(AggregateException(DivideByZeroException))です。私が見る唯一の違いは、内部のAggregateExceptionには、最初の例ではスタックトレースが設定されており(スローされたため)、2番目の例ではスタックトレースが設定されていないことです。

どちらももう一方よりも大幅に高速になる可能性は低いですが、不必要なスローを避けるために、個人的には2番目の方が好きです。

継続が結果を返すようなことをしました。私はそれを呼び出しSelect、前のタスクがキャンセルされた場合を処理し、結果の代わりに、または結果に加えて例外を変更するためのオーバーロードを提供し、ExecuteSynchronouslyオプションを使用しました。継続自体がタスクを返す場合、代わりにこの記事Thenのコードに基づいてそれを呼び出しました

于 2012-08-11T03:41:24.487 に答える