38

Sto usando il TPL (Task Parallel Library) in .NET 4.0. Voglio essere in grado di centralizzare la logica di gestione di tutte le eccezioni non gestite utilizzando l'evento Thread.GetDomain().UnhandledException. Tuttavia, nella mia applicazione, l'evento non viene mai attivato per i thread iniziati con il codice TPL, ad es. Task.Factory.StartNew(...). L'evento è effettivamente licenziato se uso qualcosa come new Thread(threadStart).Start().Come gestire tutte le eccezioni non gestite quando si utilizza Task Parallel Library?

This MSDN article suggerisce di utilizzare Task # Wait() per catturare lo AggregateException quando si lavora con TPL, ma non è quello che voglio perché non è un meccanismo abbastanza "centralizzato".

Qualcuno prova lo stesso problema o è solo io? Hai qualche soluzione per questo?

risposta

21

Sembra che non ci sia un modo integrato per gestire questo (e nessuna risposta a questa domanda dopo quasi 2 settimane). Ho già implementato un codice personalizzato per occuparmene. La descrizione della soluzione è piuttosto lunga, quindi l'ho postata nel mio blog. Fare riferimento a this post se sei interessato.

Aggiornamento 5/7/2010: Ho trovato un modo migliore per farlo, facendo uso di continuazione delle attività. Creo un class ThreadFactory che espone l'evento Errore che può essere sottoscritto da un gestore di livello superiore e fornisce i metodi per avviare un'attività associata alla corretta continuazione.
Il codice viene inviato here.

Aggiornamento 18/04/2011: Codice postale dal post del blog come da commento di Nifle.

internal class ThreadFactory 
{ 
    public delegate void TaskError(Task task, Exception error); 

    public static readonly ThreadFactory Instance = new ThreadFactory(); 

    private ThreadFactory() {} 

    public event TaskError Error; 

    public void InvokeError(Task task, Exception error) 
    { 
     TaskError handler = Error; 
     if (handler != null) handler(task, error); 
    } 

    public void Start(Action action) 
    { 
     var task = new Task(action); 
     Start(task); 
    } 

    public void Start(Action action, TaskCreationOptions options) 
    { 
     var task = new Task(action, options); 
     Start(task); 
    } 

    private void Start(Task task) 
    { 
     task.ContinueWith(t => InvokeError(t, t.Exception.InnerException), 
          TaskContinuationOptions.OnlyOnFaulted | 
          TaskContinuationOptions.ExecuteSynchronously); 
     task.Start(); 
    } 
} 
+1

La soluzione aggiornata funziona benissimo per quanto posso dire. Perché così poche persone hanno questo problema? –

+0

Sarei molto gentile da parte tua se potessi includere anche il codice dal tuo blog qui. – Nifle

+0

@Buu Nguyen Ciao, ho fatto qualcosa basandomi sul tuo approccio qui: http://stackoverflow.com/questions/11831844/unobservedtaskexception-being-throw-but-it-is-handled-by-a-taskscheduler-unobser/11908212 # 11908212 Grazie mille. Sperando che C# abbia qualcosa di meglio. – newway

32

penso TaskScheduler.UnobservedTaskException Event è ciò che si vuole:

si verifica quando un guasto Task's un'eccezione inosservata sta per innescare la politica di escalation eccezione, che, per impostazione predefinita, potrebbe terminare il processo .

Quindi, questo evento è simile a DomainUnhandledException che hai menzionato nella domanda ma si verifica solo per le attività.

BTW nota che la politica di eccezione non osservata (sì, questa non è un'eccezione non osservata, i ragazzi della MS hanno inventato una nuova parola ... di nuovo), modificata da .NET 4.0 a .NET 4.5. In .NET 4.0 l'eccezione non osservata porta alla terminazione del processo ma in .NET 4.5 - no. Questo è tutto perché nuove cose asincrone che avremo in C# 5 e VB 11.

+0

Grazie, Sergey. Ne sono consapevole, ma per qualche motivo non ha funzionato per me http://www.buunguyen.net/blog/handle-all-uncaught-exceptions-thrown-when-using-task-parallel-library. html # comment-7259 –

+8

Il problema è che l'evento 'UnobservedTaskException' viene sollevato solo quando l'oggetto' Task' in errore è finalizzato. E la finalizzazione dell'oggetto non è semplicemente garantita per l'esecuzione. –

+0

Davvero utile menzionare che la politica delle eccezioni non osservate era stata modificata in .NET 4.5 – stukselbax

10

Vedo due opzioni che possono essere utilizzate ai fini della centralizzazione della gestione delle eccezioni in TPL: 1. Utilizzo di Eccezione attività non osservata evento di Utilità di pianificazione. 2. Utilizzo di continuazioni per attività con stato di errore.

Utilizzo dell'evento Eccezione attività non osservata di Utilità di pianificazione.

L'utilità di pianificazione ha un evento UnobservedTaskException a cui è possibile iscriversi utilizzando l'operatore + =.

  • Nota 1: Nel corpo del gestore è necessario fare la chiamata SetObserved() su argomenti UnobservedTaskExceptionEventArgs per notificare scheduler che un'eccezione è stata gestita.
  • Nota 2: il gestore viene chiamato quando le attività sono state raccolte dal garbage collector.
  • Nota 3: Se si attende l'attività, si sarà comunque obbligati a proteggere l'attesa tramite try/catch block.
  • Nota 4: la politica predefinita per le eccezioni di attività non gestite in .Net 4.0 e 4.5 è diversa.

Riepilogo: questo approccio è utile per le attività "fire-and-forget" e per l'acquisizione di eccezioni evase dal criterio di gestione delle eccezioni centralizzato.

Utilizzo di continuazioni per attività con stato di errore.

Con TPL è possibile associare azioni all'attività utilizzando il metodo ContinueWith() che accetta l'azione di collegamento e l'opzione di continuazione. Questa azione verrà chiamata dopo la chiusura dell'attività e solo nei casi specificati dall'opzione. In particolare:

t.ContinueWith(c => { /* exception handling code */ }, TaskContinuationOptions.OnlyOnFaulted); 

installa la continuazione con il codice di gestione delle eccezioni al Task t. Questo codice verrà eseguito solo nel caso in cui l'attività t sia stata interrotta a causa dell'eccezione non gestita.

  • Nota 1: Ottieni il valore di eccezione nel codice di gestione delle eccezioni. Altrimenti sarà gorgogliato.
  • Nota 2: il codice di gestione delle eccezioni verrà chiamato immediatamente dopo la chiusura dell'attività.
  • Nota 3: Se l'eccezione è stata ottenuta nel codice di gestione delle eccezioni, verrà considerata come gestita, il blocco try/catch sull'attesa in attesa non sarà in grado di catturarlo.

Penso che per la gestione centralizzata delle eccezioni sia preferibile utilizzare Attività personalizzate ereditate da Attività con gestore di eccezioni aggiunto via continuazione. E accompagna questo approccio utilizzando l'evento Eccezione attività non osservato di Utilità di pianificazione per rilevare i tentativi di utilizzare attività non personalizzate.