Как сохранить ожидаемое поведение с TaskCompletionSource.SetException?
(Это новая попытка в этом вопросе, которая теперь демонстрирует проблему лучше.)
Допустим, у нас есть ошибочная задача (var faultedTask = Task.Run(() => { throw new Exception("test"); });
) и мы ждем этого. await
распакует AggregateException
и бросить основное исключение. Это бросит faultedTask.Exception.InnerExceptions.First()
,
Согласно исходному коду для ThrowForNonSuccess
это будет сделано путем выполнения любого сохраненного ExceptionDispatchInfo
по-видимому, чтобы сохранить хорошие следы стека. Он не будет распаковывать AggregateException
если нет ExceptionDispatchInfo
,
Один этот факт меня удивил, потому что в документации говорится, что первое исключение выдается всегда: https://msdn.microsoft.com/en-us/library/hh156528.aspx?f=255&MSPPError=-2147217396 Оказывается, await
может бросить AggregateException
Впрочем, это не документированное поведение.
Это становится проблемой, когда мы хотим создать прокси-задачу и установить ее исключение:
var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
await proxyTcs.Task;
Это кидает AggregateException
в то время как await faultedTask;
бросил бы тестовое исключение.
Как я могу создать прокси-задачу, которую я могу выполнить по желанию и которая будет отражать поведение исключений, которое было у исходной задачи?
Исходное поведение:
await
бросит первое внутреннее исключение.- Все исключения по-прежнему доступны через
Task.Exception.InnerExceptions
, (Более ранняя версия этого вопроса не учитывала это требование.)
Вот тест, который суммирует результаты:
[TestMethod]
public void ExceptionAwait()
{
ExceptionAwaitAsync().Wait();
}
static async Task ExceptionAwaitAsync()
{
//Task has multiple exceptions.
var faultedTask = Task.WhenAll(Task.Run(() => { throw new Exception("test"); }), Task.Run(() => { throw new Exception("test"); }));
try
{
await faultedTask;
Assert.Fail();
}
catch (Exception ex)
{
Assert.IsTrue(ex.Message == "test"); //Works.
}
Assert.IsTrue(faultedTask.Exception.InnerExceptions.Count == 2); //Works.
//Both attempts will fail. Uncomment attempt 1 to try the second one.
await Attempt1(faultedTask);
await Attempt2(faultedTask);
}
static async Task Attempt1(Task faultedTask)
{
var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
try
{
await proxyTcs.Task;
Assert.Fail();
}
catch (Exception ex)
{
Assert.IsTrue(ex.Message == "test"); //Fails.
}
}
static async Task Attempt2(Task faultedTask)
{
var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception.InnerExceptions.First());
try
{
await proxyTcs.Task;
Assert.Fail();
}
catch (Exception ex)
{
Assert.IsTrue(ex.Message == "test"); //Works.
}
Assert.IsTrue(proxyTcs.Task.Exception.InnerExceptions.Count == 2); //Fails. Should preserve both exceptions.
}
Мотивация для этого вопроса заключается в том, что я пытаюсь создать функцию, которая будет копировать результат одной задачи в TaskCompletionSource
, Это вспомогательная функция, которая часто используется при написании функций комбинатора задач. Важно, чтобы клиенты API не могли обнаружить разницу между исходной задачей и задачей-посредником.
1 ответ
Оказывается, что await может генерировать AggregateException, что не является документированным поведением.
Нет, это поведение, когда первое вложенное исключение является AggregateException
,
В основном, когда вы звоните TaskCompletionSource.SetException(Exception)
, оно оборачивает это исключение в AggregateException
,
Если вы хотите сохранить несколько исключений, просто используйте перегрузку SetException
который принимает IEnumerable<Exception>
:
proxyTcs.SetException(faultedTask.Exception.InnerExceptions);