2010-06-09 19 views
9

Estoy usando el evento Application.ThreadException para manejar y registrar excepciones inesperadas en mi aplicación winforms.Muy extraño Comportamiento Application.ThreadException

Ahora, en algún lugar de mi solicitud, tengo el siguiente código (o más bien algo equivalente, pero este código ficticio es suficiente para reproducir mi problema):

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

Estoy esperando claramente mi Application_ThreadException controlador para pasar la excepción "test2", pero este no es siempre el caso. Normalmente, si otro hilo marshals mi código a la interfaz de usuario, mi controlador recibe la excepción de "prueba", exactamente como si no hubiera atrapado "prueba" en absoluto.

Aquí hay una breve muestra que reproduce este comportamiento. He omitido el código del diseñador.

 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 salida de este programa en mi equipo es:

test 
... here I click button1 
test2 

que he reproducido en este .net 2.0,3.5 y 4.0. ¿Alguien tiene una explicación lógica?

Respuesta

1

Excepción # 1: Invoke o BeginInvoke no se puede llamar en un control hasta que se haya creado el identificador de ventana.

Por lo tanto, no intente llamar desde el constructor. Hacerlo en 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

Respuesta modificada. –

+0

Su sugerencia tiene sentido, pero esto no cambia el comportamiento extraño. – Brann

+0

@Brann, tienes razón.El extraño comportamiento persiste. Sin embargo, lo he corregido al no hacer 'catch (Exception ex)' en el controlador de clic de botón, sino más bien 'catch'. Muy interesante. Una excepción no descendente y de Excepción lanzada. –

0

Tienes que llamar

Application.SetUnhandledExceptionMode (UnhandledExceptionMode.CatchException);

primero en su método Main().

+0

Lo he intentado sin éxito. – Brann

7

Hay un error en su código que dificulta la depuración de lo que está sucediendo: inicia el subproceso antes de que se cree el identificador del formulario. Eso hará que BeginInvoke falle. Solución:

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

Anyhoo, este es el comportamiento diseñado. El código de formularios de Windows que se ejecuta el objetivo BeginInvoke se ve así:

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

Es el exception.GetBaseException() llamar a que los tornillos hasta que su mensaje de excepción. Por qué los diseñadores de Windows Forms eligieron hacer esto no está del todo claro para mí, no hay comentarios con el código en la fuente de referencia. Solo puedo adivinar que sin eso la excepción sería más difícil de depurar, en caso de que sea provocada por el código de plomería de Windows Forms en lugar del código de la aplicación. No es una gran explicación.

Ellos ya han dicho que ellos won't fix it, quizás usted podría agregar su voto. No te hagas ilusiones.

La solución alternativa es no establecer la InnerException. No es una gran opción, por supuesto.

+0

exactamente la respuesta que estaba buscando; Gracias. – Brann

+1

La llamada 'GetBaseException' está diseñada para desenvolver' TargetInvocationException's de 'Invoke'. – SLaks

+3

Eso no es todo. Control.BeginInvoke() no es lo mismo que Delegate.BeginInvoke(). No hace coincidir la excepción con la persona que llama, por ejemplo. –