Comportamento Application.ThreadException molto strano

Sto usando l’evento Application.ThreadException per gestire e registrare eccezioni impreviste nella mia applicazione winforms.

Ora, da qualche parte nella mia applicazione, ho il seguente codice (o piuttosto qualcosa di equivalente, ma questo codice fittizio è sufficiente per riprodurre il mio problema):

try { throw new NullReferenceException("test"); } catch (Exception ex) { throw new Exception("test2", ex); } 

Mi aspetto chiaramente che il mio gestore Application_ThreadException passi l’eccezione “test2”, ma non è sempre così. In genere, se un altro thread esegue il marshalling del mio codice sull’interfaccia utente, il gestore riceve l’eccezione “test”, esattamente come se non avessi rilevato il “test”.

Ecco un breve esempio che riproduce questo comportamento. Ho omesso il codice del designer.

  static class Program { [STAThread] static void Main() { Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); //Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); // has no impact in this scenario, can be commented. AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { //this handler is never called } static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { Console.WriteLine(e.Exception.Message); } } public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click+=new EventHandler(button1_Click); } protected override void OnLoad(EventArgs e) { System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx)); t.Start(); } private void button1_Click(object sender, EventArgs e) { try { throw new NullReferenceException("test"); } catch (Exception ex) { throw new Exception("test2", ex); } } void ThrowEx() { this.BeginInvoke(new EventHandler(button1_Click)); } } 

L’output di questo programma sul mio computer è:

 test ... here I click button1 test2 

Ho riprodotto questo su .net 2.0,3.5 e 4.0. Qualcuno ha una spiegazione logica?

C’è un bug nel codice che rende difficile eseguire il debug di ciò che sta succedendo: si avvia il thread prima che venga creato l’handle del modulo. Ciò farà sì che BeginInvoke non riesca. Difficoltà:

  protected override void OnLoad(EventArgs e) { System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx)); t.Start(); } 

Anyhoo, questo è un comportamento progettato. Il codice Windows Form che esegue il target BeginInvoke ha il seguente aspetto:

  try { this.InvokeMarshaledCallback(tme); } catch (Exception exception) { tme.exception = exception.GetBaseException(); } ... if ((!NativeWindow.WndProcShouldBeDebuggable && (tme.exception != null)) && !tme.synchronous) { Application.OnThreadException(tme.exception); } 

È la chiamata exception.GetBaseException () che elimina il messaggio di eccezione. Perché i progettisti di Windows Form hanno scelto di farlo non è abbastanza chiaro per me, non vi è alcun commento con il codice nella sorgente di riferimento. Posso solo supporre che senza di essa l’eccezione sarebbe più difficile da eseguire il debug, nel caso in cui venga sollevata dal codice dell’impianto idraulico di Windows Form invece del codice dell’applicazione. Non una grande spiegazione.

Hanno già detto che non lo risolveranno , forse potresti aggiungere il tuo voto. Non ti illudere.

La soluzione alternativa consiste nel non impostare InnerException. Non è una big opzione, ovviamente.

Eccezione 1: Invoke o BeginInvoke non possono essere richiamati su un controllo finché non è stato creato l’handle della finestra.

Quindi, non tentare di chiamare dal costruttore. Fatelo su OnLoad() :

 public partial class Form1 : Form { public Form1() { InitializeComponent(); this.Load += new EventHandler(Form1_Load); button1.Click += new EventHandler(button1_Click); } private void Form1_Load(object sender, EventArgs e) { System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx)); t.Start(); } ... } 

Devi chiamare

Application.SetUnhandledExceptionMode (UnhandledExceptionMode.CatchException);

prima nel tuo metodo Main ().