2010-06-09 5 views
9

J'utilise l'événement Application.ThreadException pour gérer et enregistrer les exceptions inattendues dans mon application WinForms.Très étrange comportement Application.ThreadException

Maintenant, quelque part dans ma demande, j'ai le code suivant (ou plutôt quelque chose d'équivalent, mais ce code fictif est suffisant pour reproduire mon problème):

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

Je me attends clairement mon Application_ThreadException handler à passer l'exception "test2", mais ce n'est pas toujours le cas. En règle générale, si un autre thread amène mon code à l'interface utilisateur, mon gestionnaire reçoit l'exception «test», exactement comme si je n'avais pas du tout attrapé «test».

Voici un court exemple reproduisant ce comportement. J'ai omis le code du concepteur.

 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)); 
    } 
} 

La sortie de ce programme sur mon ordinateur est:

test 
... here I click button1 
test2 

J'ai reproduit ce sur .net 2.0,3.5 et 4.0. Est-ce que quelqu'un a une explication logique?

Répondre

1

L'exception # 1: Invoke ou BeginInvoke ne peut pas être appelée sur un contrôle tant que la poignée de la fenêtre n'a pas été créée. Par conséquent, n'essayez pas d'appeler depuis le constructeur.

Faites-le dans 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(); 
    } 

    ... 
} 
+0

Réponse modifiée. –

+0

Votre suggestion a un sens, mais cela ne change pas le comportement étrange. – Brann

+0

@Brann, vous avez raison.Le comportement étrange persiste. Cependant, je l'ai corrigé en ne faisant pas 'catch (Exception ex)' dans le gestionnaire de clic de bouton, mais plutôt juste 'catch'. Très intéressant. Une exception non decendand d'Exception étant levée. –

0

Vous devez appeler

Application.SetUnhandledExceptionMode (UnhandledExceptionMode.CatchException);

d'abord dans votre méthode Main().

+0

J'ai essayé, sans succès. – Brann

7

Il y a un bogue dans votre code qui rend difficile le débogage de ce qui se passe: vous démarrez le thread avant que le Handle du formulaire ne soit créé. Cela fera échouer BeginInvoke. Correction:

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

Anyhoo, ce comportement est conçu. Le code Windows Forms qui exécute la cible BeginInvoke ressemble à ceci:

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

Il est le exception.GetBaseException() qui plisse votre message d'exception. Pourquoi les concepteurs de Windows Forms ont choisi de faire cela n'est pas très clair pour moi, il n'y a pas de commentaire avec le code dans la source de référence. Je peux seulement deviner que sans cela, l'exception serait plus difficile à déboguer, au cas où elle serait déclenchée par le code de plomberie Windows Forms au lieu du code de l'application. Pas une bonne explication.

Ils ont déjà dit qu'ils won't fix it, peut-être que vous pourriez ajouter votre vote. Ne sois pas optimiste.

La solution de contournement consiste à ne pas définir le InnerException. Pas une bonne option bien sûr.

+0

exactement la réponse que je cherchais; Merci. – Brann

+1

L'appel 'GetBaseException' est conçu pour déplier' TargetInvocationException' de 'Invoke'. – SLaks

+3

Ce n'est pas ça. Control.BeginInvoke() n'est pas du tout identique à Delegate.BeginInvoke(). Il ne rassemble pas l'exception à l'appelant, pour un. –