2014-05-20 11 views
8

Io uso un insieme di attività, a volte, e al fine di assicurarsi che essi sono tutti atteso che usano questo approccio:Perché questa eccezione non viene lanciata?

public async Task ReleaseAsync(params Task[] TaskArray) 
{ 
    var tasks = new HashSet<Task>(TaskArray); 
    while (tasks.Any()) tasks.Remove(await Task.WhenAny(tasks)); 
} 

e quindi chiamare in questo modo:

await ReleaseAsync(task1, task2, task3); 
//or 
await ReleaseAsync(tasks.ToArray()); 

Tuttavia, di recente Ho notato alcuni comportamenti strani e ho impostato se c'era un problema con il metodo ReleaseAsync. Sono riuscito a ridurlo a questa semplice demo, viene eseguito in linqpad se si include System.Threading.Tasks. Funzionerà anche leggermente modificato in un'app console o in un controller mvc asp.net.

async void Main() 
{ 
Task[] TaskArray = new Task[]{run()}; 
var tasks = new HashSet<Task>(TaskArray); 
while (tasks.Any<Task>()) tasks.Remove(await Task.WhenAny(tasks)); 
} 

public async Task<int> run() 
{ 
return await Task.Run(() => { 
    Console.WriteLine("started"); 
    throw new Exception("broke"); 
    Console.WriteLine("complete"); 
    return 5; 
}); 
} 

Quello che non capisco è perché l'eccezione non si presenta mai da nessuna parte. Avrei pensato che se i Task con l'eccezione fossero attesi, si sarebbero buttati. Sono stato in grado di confermare questo sostituendo il ciclo while con un semplice per ogni in questo modo:

foreach(var task in TaskArray) 
{ 
    await task;//this will throw the exception properly 
} 

La mia domanda è, perché non Nell'esempio mostrato generare l'eccezione correttamente (che non mostra mai da nessuna parte).

+0

possibile duplicato (http://stackoverflow.com/questions/22856052/how- to-handle-task-factory-startnew-exception) –

+0

Qualsiasi motivo per non utilizzare 'Task.WhenAll'? –

+0

@PauloMorgado - Sì, questi erano legati alle risorse gestite e volevo rilasciarli quando sono diventati disponibili invece di aspettare che tutti li completassero e poi li rilasciassero. –

risposta

9

TL; DR: run() genera l'eccezione, ma si sta in attesa di WhenAny(), che non un'eccezione stessa.


la documentazione MSDN per WhenAny stati:

Il compito tornato completerà quando uno dei compiti in dotazione è stata completata. L'attività restituita termina sempre nello stato RanToCompletion con il suo Result impostato sulla prima attività da completare. Questo è vero anche se il primo compito da completare termina nello stato Annullato o Faultato.

In sostanza, ciò che accade è che l'attività restituita da WhenAny ingerisce semplicemente l'attività in errore. Si preoccupa solo del fatto che il compito è finito, non che è stato completato con successo. Quando si attende l'attività, si completa semplicemente senza errori, perché è l'attività interna che ha riscontrato un errore e non quella che si sta aspettando.

10

Un Task non essere awaited o meno utilizzando il metodo Wait() o Result(), inghiottire l'eccezione di default. Questo comportamento può essere modificato al modo in cui è stato eseguito in .NET 4.0 interrompendo il processo in esecuzione una volta che il Task era GC. È possibile impostare nel vostro app.config come segue:

<configuration> 
    <runtime> 
     <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration> 

Una citazione da this post sul blog dal team di programmazione parallela in Microsoft:

coloro che hanno familiarità con le attività in .NET 4 saprà che la TPL ha la nozione di eccezioni "non osservate". Questo è un compromesso tra due obiettivi di progettazione concorrenti in TPL: supportare il marshalling delle eccezioni non gestite dall'operazione asincrona al codice che ne consuma il completamento/output e seguire i criteri di escalation delle eccezioni .NET standard per le eccezioni non gestite dal codice dell'applicazione. Fin da .NET 2.0, le eccezioni che non vengono gestite sui thread appena creati, negli elementi di lavoro ThreadPool e simili, hanno come conseguenza il comportamento di escalation delle eccezioni predefinito, che è il blocco del processo. Ciò è generalmente auspicabile, poiché le eccezioni indicano che qualcosa è andato storto e il crashing aiuta gli sviluppatori a identificare immediatamente che l'applicazione è entrata in uno stato inaffidabile. Idealmente, le attività seguiranno questo stesso comportamento. Tuttavia, le attività vengono utilizzate per rappresentare operazioni asincrone con le quali il codice viene successivamente aggiunto e se tali operazioni asincrone comportano eccezioni, tali eccezioni devono essere sottoposte a un marshalling nel punto in cui il codice di join è in esecuzione e consumano i risultati dell'operazione asincrona. Ciò significa intrinsecamente che TPL deve bloccare queste eccezioni e trattenerle fino al momento in cui possono essere nuovamente generate quando il codice che consuma accede all'attività. Poiché ciò impedisce il criterio di escalation predefinito, .NET 4 ha applicato la nozione di eccezioni "non osservate" per integrare la nozione di eccezioni "non gestite". Un'eccezione "non osservata" è quella che viene archiviata nell'attività ma che non viene mai esaminata in alcun modo dal codice che consuma. Esistono molti modi per osservare l'eccezione, inclusa Wait() sull'attività, accedere al risultato di un'attività, esaminare la proprietà Exception dell'attività e così via. Se il codice non osserva mai un'eccezione di un'attività, quando l'attività scompare, viene sollevata la TaskScheduler.UnobservedTaskException, dando all'applicazione una possibilità in più di "osservare" l'eccezione. E se l'eccezione rimane ancora inosservata, la politica di escalation delle eccezioni viene quindi abilitata dall'eccezione non gestita sul thread del finalizzatore.

+0

Credo che la risposta di Yuval spieghi il comportamento che stai vedendo. Inoltre, l'eccezione dovrebbe essere accessibile nell'oggetto task restituito da run(). – Naylor

4

Dal commento:

questi [attività] sono stati legati alle risorse gestite e volevo rilasciare quando si sono resi disponibili, invece di aspettare per tutti loro per completare e poi rilasciare.

Usando un metodo di supporto async void può darvi il comportamento desiderato sia per la rimozione delle attività completate dalla lista e subito generare eccezioni non osservati:

public static class TaskExt 
{ 
    public static async void Observe<TResult>(Task<TResult> task) 
    { 
     await task; 
    } 

    public static async Task<TResult> WithObservation(Task<TResult> task) 
    { 
     try 
     { 
      return await task; 
     } 
     catch (Exception ex) 
     { 
      // Handle ex 
      // ... 

      // Or, observe and re-throw 
      task.Observe(); // do this if you want to throw immediately 

      throw; 
     } 
    } 
} 

Poi il codice potrebbe essere simile a questo (non testato):

async void Main() 
{ 
    Task[] TaskArray = new Task[] { run().WithObservation() }; 
    var tasks = new HashSet<Task>(TaskArray); 
    while (tasks.Any<Task>()) tasks.Remove(await Task.WhenAny(tasks)); 
} 

.Observe() sarà ri-lanciare eccezioni del compito subito "out-of-band", utilizzando SynchronizationContext.Post se il thread chiamante ha un sincro contesto di nization, o utilizzando ThreadPool.QueueUserWorkItem in caso contrario. È possibile gestire tali eccezioni "fuori banda" con AppDomain.CurrentDomain.UnhandledException).

ho descritto questo in maggiori dettagli qui: [? Come gestire eccezione Task.Factory.StartNew]

TAP global exception handler

Problemi correlati