Подтвердить что ты не робот

Повторное удаление предыдущего исключения внутри ContinueWith

Введение

После некоторого недоумения над моим кодом я обнаружил, что исключения не обязательно распространяются через 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>();
     return task.ContinueWith(t =>
     {
         if(t.IsFaulted) tcs.TrySetException(t.Exception);
         continuation(t);
         tcs.TrySetResult(default(object));
     });
     return tcs.Task;
}

Вопрос

Являются ли эти две версии строго эквивалентными? Или есть тонкая разница между throw t.Exception и tcs.TrySetException(t.Exception)?

Кроме того, делает ли тот факт, что, по-видимому, только один человек во всем Интернете, который сделал это, указывает, что мне не хватает идиоматического способа сделать это?

4b9b3361

Ответ 1

Разница между ними тонкая. В первом примере вы выбрасываете исключение, возвращаемое из задачи. Это вызовет обычное бросание и ловушку исключения в CLR, ContinueWith будет захватывать и обертывать его и передавать его следующей задаче в цепочке.

Во втором вы вызываете TrySetException, который все равно будет обертывать исключение и передавать его следующей задаче в цепочке, но не вызывает никакой логики try/catch.

Конечный результат после одного ContinueWithEx равен AggregateException(AggregateException(DivideByZeroException)). Единственное различие, которое я вижу, заключается в том, что внутреннее AggregateException имеет трассировку стека, установленную в первом примере (поскольку она была выбрана), а во втором примере нет трассировки стека.

Ни один из них не может быть значительно быстрее, чем другой, но я бы предпочел, чтобы второй избежал ненужных бросков.

Я сделал что-то подобное, когда продолжение вернуло результат. Я назвал его Select, обработал случаи отмены предыдущей задачи, предоставил перегрузки для изменения исключения вместо или в дополнение к результату и использовал параметр ExecuteSynchronously. Когда продолжение само по себе вернет задачу, я назвал это Then вместо этого на основе кода из этой статьи