13

(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 è:

  1. await getterà la prima eccezione interna.
  2. 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.

+1

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 –

+0

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? –

+0

@KirillShlenskiy utilizza API interne. Inoltre, il chiamante del mio codice non dovrebbe aver bisogno di sapere nulla di speciale. – usr

risposta

8

Si scopre che l'attesa può lanciare AggregateException, che non è un comportamento documentato.

No, questo è il comportamento quando la prima eccezione annidata è un AggregateException.

Fondamentalmente, quando si chiama TaskCompletionSource.SetException(Exception), questa eccezione viene applicata in un AggregateException.

Se si desidera conservare multipla eccezioni, basta usare il sovraccarico di SetException che accetta un IEnumerable<Exception>:

proxyTcs.SetException(faultedTask.Exception.InnerExceptions); 
Problemi correlati