Каков правильный способ распространения исключений в цепях продолжения?
Каков правильный способ распространения исключений в цепях продолжения?
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(/*...*/);
Ответы
Ответ 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