2010-06-09 2 views
9

Я использую событие Application.ThreadException для обработки и регистрации неожиданных исключений в моем приложении winforms.Очень странное поведение Application.ThreadException

Теперь, где-то в моем приложении, у меня есть следующий код (или, скорее, что-то эквивалент, но этот фиктивный код достаточно, чтобы воспроизвести мой вопрос):

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

Я явно ожидал моего Application_ThreadException обработчика, которому нужно передать исключение «test2», но это не всегда так. Как правило, , если другой поток упорядочивает мой код в пользовательском интерфейсе, мой обработчик получает «тестовое» исключение, точно так же, как если бы я не поймал «тест» вообще.

Ниже приведен пример, воспроизводящий такое поведение. Я пропустил код дизайнера.

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

Вывод этой программы на моем компьютере:

test 
... here I click button1 
test2 

Я воспроизвел на .net 2.0,3.5 и 4.0. У кого-то есть логическое объяснение?

ответ

1

Исключение №1: Invoke или BeginInvoke не может быть вызвано на элемент управления до тех пор, пока не будет создан дескриптор окна.

Итак, не пытайтесь вызвать из конструктора. Делайте это в 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

Изменен ответ. –

+0

Ваше предложение имеет смысл, но это не меняет странного поведения. – Brann

+0

@Brann, вы правы.Странное поведение сохраняется. Тем не менее, я исправил это, не делая 'catch (Exception ex)' в обработчике щелчка, а просто 'catch'. Очень интересно. Исключено исключение decendand Exception. –

0

Вы должны вызвать

Application.SetUnhandledExceptionMode (UnhandledExceptionMode.CatchException);

первый в вашем основном() способ.

+0

Я пробовал это, безуспешно. – Brann

7

В вашем коде есть ошибка, из-за которой сложно отлаживать то, что происходит: вы запускаете поток до создания Ручки формы. Это приведет к сбою BeginInvoke. Fix:

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

Anyhoo, это спроектированное поведение. Формы кода для Windows, который работает в цель BeginInvoke выглядит следующим образом:

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

Это exception.GetBaseException() вызов, что облажался ваше сообщение об исключении. Почему разработчики Windows Forms решили сделать это, мне не совсем понятно, комментариев с кодом в Reference Source нет. Я могу только догадываться, что без него исключение будет сложнее отлаживать, если оно будет поднято кодом сантехники Windows Forms вместо кода приложения. Не очень хорошее объяснение.

Они уже говорили, что они won't fix it, возможно, вы можете добавить свой голос. Не возлагайте надежды.

Обходным путем является не устанавливать InnerException. Разумеется, это отличный вариант.

+0

именно ответ, который я искал; Благодарю. – Brann

+1

Вызов 'GetBaseException' предназначен для разворачивания' TargetInvocationException' из 'Invoke'. – SLaks

+3

Не это. Control.BeginInvoke() совсем не совпадает с Delegate.BeginInvoke(). Это не исключает исключение для вызывающего, для одного. –

Смежные вопросы