(Jest to nowa próba na to pytanie, które teraz pokazuje problem lepiej).Jak zachować oczekiwanie na zachowanie z TaskCompletionSource.SetException?
Powiedzmy mamy nic zarzucić zadania (var faultedTask = Task.Run(() => { throw new Exception("test"); });
), a my go czekają. await
rozpakuje AggregateException
i wyrzuci podstawowy wyjątek. To rzuci faultedTask.Exception.InnerExceptions.First()
.
Zgodnie z kodem źródłowym dla ThrowForNonSuccess
zrobi to, wykonując każdy zapisany ExceptionDispatchInfo
, prawdopodobnie w celu zachowania ładnych śladów stosu. To nie rozpakuje AggregateException
, jeśli nie ma ExceptionDispatchInfo
.
Już sam ten fakt był zaskoczeniem dla mnie, ponieważ dokumentacja wskazuje, że pierwszy wyjątek jest zawsze rzucony: https://msdn.microsoft.com/en-us/library/hh156528.aspx?f=255&MSPPError=-2147217396 Okazuje się, że await
może rzucać AggregateException
, choć, co nie jest udokumentowane zachowanie.
Staje się to problemem, gdy chcemy utworzyć zadanie proxy i ustawić to wyjątek:
var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
await proxyTcs.Task;
To rzuca AggregateException
natomiast await faultedTask;
byłby rzucony wyjątek testową.
Jak mogę utworzyć zadanie proxy, które mogę wykonać w dowolnym momencie i które będzie odzwierciedlało zachowanie wyjątku, które miało oryginalne zadanie?
Oryginalny zachowanie:
await
rzuci pierwszy wyjątek wewnętrzną.- Wszystkie wyjątki są nadal dostępne za pośrednictwem
Task.Exception.InnerExceptions
. (. Wcześniejsza wersja tej kwestii pominąć ten wymóg)
Oto test, który podsumowuje wyniki:
[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.
}
Motywacja na to pytanie jest to, że próbuję skonstruować funkcję skopiuje wynik jednego zadania na TaskCompletionSource
. Jest to funkcja pomocnicza, która jest często używana podczas pisania funkcji kombinatorów zadań. Ważne jest, aby klienci korzystający z interfejsu API nie mogli wykryć różnicy między pierwotnym zadaniem a zadaniem proxy.
To zawsze jest zabawa, aby przeczytać nowe pytanie z 50k + użytkownik rep, 90% czas mi pójść "ooh, chciałbym też o tym wiedzieć" i skończyć na faworyzowaniu pytania –
Jeśli chodzi o semantykę "oczekuj", której szukasz w odniesieniu do wyjątków, dlaczego nie po prostu pójść prosto do źródła (http: // referencesource .microsoft.com/# mscorlib/system/runtime/compilerservices/TaskAwaiter.cs, ca9850c71672bd54) i skopiować, co robi 'TaskAwaiter'? –
@ Kirir Shlenskiy wykorzystuje to wewnętrzne interfejsy API. Ponadto osoba wywołująca mój kod nie musi znać niczego specjalnego. – usr