(Это новая попытка в этом вопросе, которая теперь лучше показывает проблему.)
Скажем, у нас есть сбойная задача (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 не могли обнаружить разницу между исходной задачей и задачей прокси.