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

Каков правильный способ распространения исключений в цепях продолжения?

Каков правильный способ распространения исключений в цепях продолжения?

t.ContinueWith(t2 => 
{
     if(t2.Exception != null)
         throw t2.Exception;

     /* Other async code. */
})
.ContinueWith(/*...*/);   

t.ContinueWith(t2 => 
{
     if(t2.IsFaulted)
         throw t2.Exception;

     /* Other async code. */
})
.ContinueWith(/*...*/);

t.ContinueWith(t2 => 
{
     if(t2.Exception != null)
         return t2;

     /* Other async code. */
})
.ContinueWith(/*...*/);   

t.ContinueWith(t2 => 
{
     if(t2.IsFaulted)
         return t2;

     /* Other async code. */
})
.ContinueWith(/*...*/);


t.ContinueWith(t2 => 
{
     t2.Wait();

     /* Other async code. */
})
.ContinueWith(/*...*/);

t.ContinueWith(t2 => 
{     
     /* Other async code. */
}, TaskContinuationOptions.NotOnFaulted) // Don't think this one works as expected
.ContinueWith(/*...*/);
4b9b3361

Ответ 1

TaskContinuationOptions.OnlyOn... может быть проблематичным, поскольку они вызывают продолжение отмены, если их условие не выполняется. У меня были некоторые тонкие проблемы с кодом, который я написал, прежде чем я это понял.

Цепные продолжения, подобные этому, на самом деле довольно сложно добиться. Самым простым решением является использование новой функции .NET 4.5 await. Это позволяет почти игнорировать тот факт, что вы пишете асинхронный код. Вы можете использовать блоки try/catch так же, как и в синхронном эквиваленте. Для .NET 4 это доступно с помощью асинхронного таргетинга.

Если вы используете .NET 4.0, наиболее простой подход заключается в доступе к Task.Result из предварительной задачи в каждом продолжении или, если он не возвращает результат, используйте Task.Wait(), как в своем примере код. Однако вы, вероятно, получите вложенное дерево объектов AggregateException, которое вам нужно будет разгадать позже, чтобы перейти к "реальному" исключению. (Опять же,.NET 4.5 делает это проще. Хотя Task.Result throws AggregateException, Task.GetAwaiter().GetResult(), что в противном случае эквивалентно, генерирует основное исключение.)

Повторяем, что на самом деле это не тривиальная проблема, вам могут быть интересны статьи Эрика Липперта об обработке исключений в асинхронном коде С# 5 здесь и здесь.

Ответ 2

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

task.ContinueWith(t =>
{
    //do stuff
}, TaskContinuationOptions.OnlyOnRanToCompletion);

Если вы явно хотите обработать случай исключения (возможно, для ведения журнала, измените исключение, которое будет выбрано каким-то другим типом исключения (возможно, с дополнительной информацией или скрыть информацию, которая не должна быть раскрыта)), тогда вы можете добавить продолжение с опцией OnlyOnFaulted (возможно, в дополнение к продолжению обычного случая).

Ответ 3

Опция OnlyOnFaulted предназначена для случаев, когда предыдущий Task не мог выполнить задачу правильно. Один из способов - перестроить исключение в каждой задаче продолжения, но это будет плохой дизайн.

Если вы хотите передать исключение точно так же, как обычный объект, обратитесь к нему как к одному. Пусть Task возвращает исключение и использует свойство Task.Result в продолжении.

Ответ 4

Подход, который мы теперь используем в openstack.net SDK, - это методы расширения в CoreTaskExtensions.cs.

Методы бывают двух форм:

  • Then: продолжение возвращает a Task, а Unwrap() вызывается автоматически.
  • Select: продолжение возвращает объект, и вызов Unwrap() не выполняется. Этот метод предназначен только для облегченных продолжений, поскольку он всегда указывает TaskContinuationOptions.ExecuteSynchronously.

Эти методы имеют следующие преимущества:

  • Предотвращает вызов метода продолжения при аннулировании или аннулировании антецедента.
  • Вместо этого результат антецедента становится результатом цепной операции (точно сохраняя информацию об исключении, не обертывая исключение в нескольких слоях AggregateException).
  • Позволяет вызывающим абонентам записывать методы продолжения, которые поддерживают ошибочные антецеденты, и в этом случае только отмененные антецедентные задачи обходят продолжение (укажите supportsErrors=true для методов расширения).
  • Продолжения, возвращающие Task, выполняются синхронно, и для вас вызывается Unwrap().

Следующее сравнение показывает, как мы применили это изменение к CloudAutoScaleProvider.cs, изначально использовав ContinueWith и Unwrap:
https://github.com/openstacknetsdk/openstack.net/compare/3ae981e9...299b9f67#diff-3