(Si tratta di un nuovo tentativo di questa domanda che ora dimostra la questione meglio.)Come conservare il comportamento di attesa con TaskCompletionSource.SetException?
Diciamo che abbiamo un compito guasto (var faultedTask = Task.Run(() => { throw new Exception("test"); });
) e attendiamo esso. await
decomprimerà il AggregateException
e genererà l'eccezione sottostante. Lanciare faultedTask.Exception.InnerExceptions.First()
.
In base al codice sorgente per ThrowForNonSuccess
lo farà eseguendo qualsiasi ExceptionDispatchInfo
memorizzato presumibilmente per conservare le tracce dello stack. Se non è presente lo ExceptionDispatchInfo
, non è possibile decomprimere lo AggregateException
.
Questo fatto da solo è stato sorprendente per me, perché la documentazione afferma che la prima eccezione è sempre gettati: https://msdn.microsoft.com/en-us/library/hh156528.aspx?f=255&MSPPError=-2147217396 Si scopre che await
possono gettare AggregateException
, però, che non è documentato il comportamento.
Questo diventa un problema quando vogliamo creare un'attività proxy e impostiamo è un'eccezione:
var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
await proxyTcs.Task;
Questo tiri AggregateException
mentre await faultedTask;
avrebbe gettato l'eccezione di prova.
Come è possibile creare un'attività proxy che posso completare a piacere e che rispecchierà il comportamento dell'eccezione che aveva l'attività originale?
Il comportamento originale è:
await
getterà la prima eccezione interna.- Tutte le eccezioni sono ancora disponibili tramite
Task.Exception.InnerExceptions
. (. Una precedente versione di questa domanda lasciato fuori questo requisito)
Ecco un test che riassume i risultati:
[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.
}
La motivazione di questa domanda è che sto cercando di costruire una funzione che copierà il risultato di un'attività su un TaskCompletionSource
. Questa è una funzione di supporto che viene utilizzata spesso durante la scrittura di funzioni combinatore di compiti. È importante che i client API non siano in grado di rilevare la differenza tra l'attività originale e un'attività proxy.
E 'sempre divertente leggere una nuova domanda da un + utente 50k rep, il 90% del ora vado "ooh, mi piacerebbe saperlo anche io" e finisco per favorire la domanda –
Se è la semantica di "attesa" che vuoi per le eccezioni, perché non andare direttamente al sorgente (http: // referenceource .microsoft.com/# mscorlib/system/runtime/compilerservices/TaskAwaiter.cs, ca9850c71672bd54) e copiare cosa 'TaskAwaiter' fa? –
@KirillShlenskiy utilizza API interne. Inoltre, il chiamante del mio codice non dovrebbe aver bisogno di sapere nulla di speciale. – usr