2016-06-05 16 views
5

Espongo un thread in primo piano e un thread in background, generando un'eccezione in ciascuno.Perché un'eccezione non gestita in questo thread in background non termina il mio processo?

using System; 
using System.Threading; 

namespace OriginalCallStackIsLostOnRethrow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       A2(); 

       // Uncomment this to see how the unhandled 
       // exception in the foreground thread causes 
       // the program to terminate 
       // An exception in this foreground thread 
       // *does* terminate the program 
       // var t = new Thread(() => { 
       //  throw new DivideByZeroException(); 
       // }); 

       // t.Start(); 
      } 
      catch (Exception ex) 
      { 
       // I am not expecting anything from the 
       // threads to come here, which is fine 
       Console.WriteLine(ex); 
      } 
      finally 
      { 
       Console.WriteLine("Press any key to exit..."); 
       Console.ReadKey(); 
      } 
     } 

     static void A2() { B2(); } 
     static void B2() { C2(); } 
     static void C2() { D2(); } 
     static void D2() 
     { 
      Action action =() => 
      { 
       Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2"); 
       throw new DivideByZeroException(); 
       Console.WriteLine("Do we get here? Obviously not!"); 
      }; 
      action.BeginInvoke(ar => Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"), null); 
     } 
    } 
} 

Come previsto, l'eccezione non gestita nel thread in primo piano termina il processo. Tuttavia, l'eccezione non gestita nel thread in background termina semplicemente il thread e non interrompe il processo, passando inosservato e senza riuscire in modo silenzioso.

Questo programma, di conseguenza, produce il seguente output:

Press any key to exit... 
D2 called on worker #6. Exception will occur while running D2 
D2 completed on worker thread #6 

Questa sfida la mia comprensione sulla gestione delle eccezioni in fili. La mia comprensione è che, indipendentemente dalla natura del thread, un'eccezione non gestita, dalla v2.0 del framework in poi, porterà il processo alla risoluzione.

Ecco una citazione da the documentation su questo argomento:

Lo stato di primo piano o sullo sfondo di un filo non influisce sulla risultato di un'eccezione non gestita nel thread. In .NET Framework versione 2.0, un'eccezione non gestita nei thread in primo piano o in background provoca la chiusura dell'applicazione. Vedere le eccezioni nei thread gestiti .

ulteriormente più, la pagina intitolata Exceptions in Managed Threads membri come segue:

partire dalla versione .NET Framework 2.0, il linguaggio comune runtime permette la maggior parte delle eccezioni non gestite in discussioni di procedere naturalmente. Nella maggior parte dei casi ciò significa che l'eccezione non gestita causa la chiusura dell'applicazione.

Questo è un cambiamento significativo rispetto alle versioni .NET Framework 1.0 e 1.1, che forniscono un backstop per molte eccezioni non gestite, ad esempio eccezioni non gestite nei thread del pool di thread. Vedere Modifica da Versioni precedenti più avanti in questo argomento.

Un'altra osservazione INTERESSANTE

È interessante notare che, se mi causa l'eccezione per essere gettato nella richiamata completamento invece che l'azione reale che si sta facendo, l'eccezione sul thread in background in quel caso fa causa una chiusura del programma. Per il codice, vedi sotto.

using System; 
using System.Threading; 

namespace OriginalCallStackIsLostOnRethrow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       // A2(); 
       A3(); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex); 
      } 
      finally 
      { 
       Console.WriteLine("Press any key to exit..."); 
       Console.ReadKey(); 
      } 
     } 

     static void A2() { B2(); } 
     static void B2() { C2(); } 
     static void C2() { D2(); } 
     static void D2() 
     { 
      Action action =() => 
      { 
       try 
       { 
        Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2"); 
        throw new DivideByZeroException(); 
        // Console.WriteLine("Do we get here? Obviously not!"); 
       } 
       catch(Exception ex) 
       { 
        Console.WriteLine(ex); 
       } 
      }; 
      action.BeginInvoke(ar => Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"), null); 
     } 

     static void A3() { B3(); } 
     static void B3() { C3(); } 
     static void C3() { D3(); } 
     static void D3() 
     { 
      Action action =() => { Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}."); }; 
      action.BeginInvoke(ar => 
      { 
       Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}. Oh, but wait! Exception!"); 

       // This one on the completion callback does terminate the program 
       throw new DivideByZeroException(); 
      }, null); 
     } 
    } 
} 

ANCORA Un'altra osservazione

ulteriori interessanti, ancora più interessante, se si gestisce l'eccezione nell'azione che si desidera eseguire utilizzando APM, nel blocco catch (impostare un punto di interruzione nella cattura blocco in D2()), lo Exception che appare non ha traccia di stack oltre al lambda invocato. Non ha assolutamente nessuna informazione nemmeno su come è arrivata lì.

considerando che il presente non è vero per le eccezioni che si intrappolare in un blocco catch nel callback di completamento, come nel caso di D3().

Sto utilizzando il compilatore C# 6.0 in Visual Studio Community 2015 Edition e il mio programma target v4.5.2 del framework .NET.

+0

@ChrisO ho fatto, ma non è questo il punto qui. Il punto è: il runtime termina il thread e mantiene il processo in corso, fallendo quindi silenziosamente, il che non era il comportamento che mi aspettavo. Che ho ricevuto un avvertimento era solo perché ho esplicitamente lanciato un'eccezione. Potrei non averlo lanciato esplicitamente e chiamato 'Console.ReadLine()' per esempio e ho diviso un numero casuale per l'input dell'utente e ho ancora ricevuto un 'FormatException' o un' DivideByZeroException'. Ciò non avrebbe cambiato la domanda attraverso di esso si sarebbe sbarazzato dell'avvertimento. –

+0

Hai ragione, l'avviso non ha importanza per il contesto della domanda. –

+1

Sono rimasto sorpreso anche da questo. Ho visto questo link che potrebbe far luce: http://stackoverflow.com/questions/6465517/why-unhandled-exception-in-a-background-thread-doesnt-crash-the-app-domain L'unica volta che ho visto qualcosa di simile è stato quando un thread in un servizio Windows C# stava chiamando 'GetDirectories' e la nostra rete pessima ha perso la connessione all'unità condivisa che il thread stava guardando. Piuttosto che fallire, il thread si è fermato e il servizio è continuato senza avvertimenti. Abbastanza fastidioso perché il thread avrebbe dovuto fare qualcosa di importante. –

risposta

3

Come PetSerAl indica nella sezione commenti della domanda, per ottenere le informazioni di eccezione, è obbligatorio chiamare EndInvoke dall'interno del callback di completamento come mostrato di seguito.

using System; 
using System.Runtime.Remoting.Messaging; 
using System.Threading; 

namespace OriginalCallStackIsLostOnRethrow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       A2(); 
       // A3(); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex); 
      } 
      finally 
      { 
       Console.WriteLine("Press any key to exit..."); 
       Console.ReadKey(); 
      } 
     } 

     static void A2() { B2(); } 
     static void B2() { C2(); } 
     static void C2() { D2(); } 
     static void D2() 
     { 
      Action action =() => 
      { 
       Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2"); 
       throw new DivideByZeroException();  
      }; 
      action.BeginInvoke(ar => 
      { 
       ((Action)((ar as AsyncResult).AsyncDelegate)).EndInvoke(ar); 

       Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"); 
      }, null); 
     } 

     static void A3() { B3(); } 
     static void B3() { C3(); } 
     static void C3() { D3(); } 
     static void D3() 
     { 
      Action action =() => { Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}."); }; 
      action.BeginInvoke(ar => 
      { 
       try 
       { 
        Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}. Oh, but wait! Exception!"); 
        throw new DivideByZeroException(); 
       } 
       catch (Exception ex) 
       { 
        throw ex; 
       } 
      }, null); 

     } 
    } 
} 

Questo è strano, e rimane ancora un mistero il motivo per cui l'analisi dello stack non si presenta se si dovesse inserire un blocco try/catch nell'azione che sta eseguendo in modo asincrono.

Mi riferisco all'assenza dello StackTrace, non all'assenza di uno stack di chiamate. :-)

enter image description here

+0

Lo stack di chiamate è lì. È solo che il tuo codice delegato è il primo fotogramma di cui hai creato il codice. Fai in modo che Visual Studio mostri il codice esterno. – usr

+0

@usr Ovviamente, lo stack di chiamate * deve * essere lì. Mi riferisco all'assenza di una traccia dello stack. Si prega di vedere l'immagine nella risposta aggiornata. :-) –

+0

@usr Vedo che il modo in cui ho chiamato il mio spazio dei nomi potrebbe averti indotto a pensare che l'ultima più piccola delle mie domande in questa domanda riguardava uno * stack di chiamate *. –