2012-09-07 8 views
18

Quando un metodo async atteso genera un'eccezione, l'eccezione viene memorizzata da qualche parte e il lancio viene ritardato. In un'applicazione WinForms o WPF, utilizza SynchronizationContext.Current per pubblicare l'eccezione. Tuttavia, ad es. un'applicazione console, lancia l'eccezione su un pool di thread e fa scendere l'applicazione.Eccezioni gestite non gestite da asincrono

Come impedire che le eccezioni generate da un metodo async provochino il rilascio dell'applicazione?

EDIT:

Appearantly il problema che sto descrivendo è perché ho voidasync metodi. Vedi i commenti.

+2

Se è in attesa un metodo asincrono, l'eccezione viene generata nel codice che la attende. Le eccezioni non gestite si comportano in questo modo solo se il metodo è "void' return. Questo è un motivo per evitare di usare il metodo 'async void' il più possibile. – svick

+1

I metodi @svick 'async void' determinano un comportamento deterministico; chiamare una funzione 'Task'-return che genera un'eccezione e non attende l'attività solleverà l'evento' UnobservedTaskException' in un punto semirandom in futuro quando gira il garbage collector, e se non fa nulla, lascia che il programma continui silenziosamente come se va tutto bene Il problema non è con i metodi 'async void', ma espongono semplicemente il vero problema. Se * non * richiami una funzione di 'Task'-return da un metodo' async', ci sono buone probabilità che tu stia facendo qualcosa di sbagliato. – hvd

+0

A proposito, solo "buone probabilità" perché ci sono alcuni casi eccezionali (non puniti) in cui va bene scartare le eccezioni, ma non molto. – hvd

risposta

10

Quando viene avviato il metodo async, acquisisce il contesto di sincronizzazione corrente. Un modo per risolvere questo problema è creare il proprio contesto di sincronizzazione che cattura l'eccezione.

Il punto qui è che il contesto messaggi di sincronizzazione della richiamata al pool di thread, ma con un try/catch intorno ad esso:

public class AsyncSynchronizationContext : SynchronizationContext 
{ 
    public override void Send(SendOrPostCallback d, object state) 
    { 
     try 
     { 
      d(state); 
     } 
     catch (Exception ex) 
     { 
      // Put your exception handling logic here. 

      Console.WriteLine(ex.Message); 
     } 
    } 

    public override void Post(SendOrPostCallback d, object state) 
    { 
     try 
     { 
      d(state); 
     } 
     catch (Exception ex) 
     { 
      // Put your exception handling logic here. 

      Console.WriteLine(ex.Message); 
     } 
    } 
} 

Nella catch sopra di voi può mettere la logica di gestione delle eccezioni.

Avanti, su ogni filo (SynchronizationContext.Current è [ThreadStatic]) in cui si desidera eseguire async metodi con questo meccanismo, è necessario impostare il contesto di sincronizzazione corrente:

SynchronizationContext.SetSynchronizationContext(new AsyncSynchronizationContext()); 

La completa Main esempio:

class Program 
{ 
    static void Main(string[] args) 
    { 
     SynchronizationContext.SetSynchronizationContext(new AsyncSynchronizationContext()); 

     ExecuteAsyncMethod(); 

     Console.ReadKey(); 
    } 

    private static async void ExecuteAsyncMethod() 
    { 
     await AsyncMethod(); 
    } 

    private static async Task AsyncMethod() 
    { 
     throw new Exception("Exception from async"); 
    } 
} 
+3

Mi dispiace ma non vedo esattamente dove il tuo SynchronizationContext posta qualcosa nel pool di thread - per quanto posso vedere tutto ciò che fa è che esegue immediatamente la funzione sul thread chiamante e restituisce. –

18

Come è possibile impedire che le eccezioni generate da un metodo asincrono causino il ribasso dell'applicazione?

Seguire queste procedure:

  1. Tutti async metodi dovrebbero tornare Task o Task<T> a meno che non abbiano per tornare void (ad esempio, i gestori di eventi).
  2. A un certo punto, è necessario await tutti i Task s restituiti dai metodi async. L'unica ragione per cui non vorresti farlo è se non ti interessa più il risultato dell'operazione (ad es. Dopo averlo annullato).
  3. Se è necessario rilevare un'eccezione da un gestore di eventi async void, rilevarlo nel gestore eventi, esattamente come si farebbe se si trattasse di codice sincrono.

È possibile trovare il mio async/await intro post utile; Copro anche molte altre buone pratiche.

+1

Capisco che il problema che sto ricevendo è che ho metodi 'async'' void'. Tuttavia, questo significa che il problema che sto descrivendo si verifica per i gestori di eventi? –

+0

Sì; per reiterare il terzo punto, se hai un gestore di eventi 'async void', probabilmente vuoi catturare le eccezioni * all'interno di * quel gestore di eventi. Un gestore di eventi sincrono avrà lo stesso identico problema: arresterà l'app se un'eccezione scappa mentre è in esecuzione su un thread del thread. –

+0

Tranne che le eccezioni vengono instradate al 'SynchronizationContext' che è stato catturato quando è stato richiamato il metodo' async'. In un'applicazione WinForms/WPF la maggior parte delle volte questo sarà un contesto di sincronizzazione corretto poiché l'evento sarà probabilmente avviato dal thread dell'interfaccia utente. Sembra che il comportamento non sia molto diverso da quello che succede quando l'evento non è 'async'. –