Обработка исключений с несколькими формами

Я вижу другое поведение с исключениями, которые пойманы или не пойманы, когда я отлаживаю, и когда я запускаю скомпилированный .exe. У меня две формы (Form1 и Form2). Form1 имеет кнопку на нем, которая создает экземпляр и вызывает ShowDialog на Form2. Form2 имеет кнопку на ней, которая преднамеренно производит деление на нулевую ошибку. Когда я отлаживаю, блок catch в Form1 попадает. Когда я запускаю скомпилированный файл .exe, он НЕ попадает, и вместо этого я получаю окно сообщения, в котором говорится: "Необработанное исключение произошло в вашем приложении. Если вы нажмете" продолжить ", приложение проигнорирует эту ошибку и попытается продолжить. нажмите" Выход ", приложение сразу закроется... Попытка деления на ноль". Мой вопрос: почему у вас возникает другое поведение при отладке и при работе с .exe? Если это ожидаемое поведение, то считалось бы необходимым разместить блоки try/catch в каждом отдельном обработчике событий? Это похоже на сумасшедшую смерть, не так ли?

Вот код для Form1.

public partial class Form1 : Form
{
    public Form1()
    {
            InitializeComponent();

    }

    private void button1_Click(object sender, EventArgs e)
    {
        try
        {
            Form2 f2 = new Form2();
            f2.ShowDialog();
        }
        catch(Exception eX)
        {
            MessageBox.Show( eX.ToString()); //This line hit when debugging only
        }
    }
}

Здесь код Form2:

public partial class Form2 : Form
{
    public Form2()
    {
            InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
            int x = 0;
            int y = 7 / x;

    }
}

Ответ 1

Я получаю то же поведение, что и вы. Я не знаю, почему это происходит, но, похоже, неплохо предположить, что в стеке вызова ShowDialog() появится исключение, генерируемое из события в форме. Было бы лучше сделать эти две вещи:

  • Поймать и обработать исключения в обработчиках событий в Form2, где имеет смысл это сделать, и когда вы можете сделать что-то значимое с исключением.
  • Добавьте обработчик исключенных обработчиков (`Application_ThreadException`) для всего вашего приложения, чтобы поймать любые необработанные исключения.

Обновление: Вот трассировка стека. Версия отладки:

System.DivideByZeroException: Attempted to divide by zero.
   at WindowsFormsApplication1.Form2.button1_Click(Object sender, EventArgs e) in ...\WindowsFormsApplication1\Form2.cs:line 27
   at System.Windows.Forms.Control.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ButtonBase.WndProc(Message& m)
   at System.Windows.Forms.Button.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.RunDialog(Form form)
   at System.Windows.Forms.Form.ShowDialog(IWin32Window owner)
   at System.Windows.Forms.Form.ShowDialog()
   at WindowsFormsApplication1.Form1.button1_Click(Object sender, EventArgs e) in ...\WindowsFormsApplication1\Form1.cs:line 45

Release:

System.DivideByZeroException: Attempted to divide by zero.
   at WindowsFormsApplication1.Form2.button1_Click(Object sender, EventArgs e) in ...\WindowsFormsApplication1\Form2.cs:line 27
   at System.Windows.Forms.Control.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ButtonBase.WndProc(Message& m)
   at System.Windows.Forms.Button.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Обратите внимание, что System.Windows.Forms.Form.ShowDialog() не находится в трассировке стека в режиме выпуска, поэтому ваш try {} catch {} ничего не делает. Также примечательно, что в случае отладки используется NativeWindow.DebuggableCallback, который предположительно предназначен для отладки, не разбивая стек, тогда как в режиме Release используется NativeWindow.Callback.

Ответ 2

Да, это по дизайну и тесно связано с тем, как работает Windows Forms. В приложении Winforms код запускается в ответ на сообщения, отправленные в активное окно Windows. Каждое собственное приложение Windows содержит цикл сообщений для обнаружения этих сообщений. Сантехника Winforms гарантирует, что один из ваших обработчиков событий будет работать в ответ; button1_Click в вашем примере кода.

Большинство элементов управления Winforms реализуют собственные обработчики событий. Например, PictureBox имеет обработчик события Paint, который обеспечивает его обратное рисование на экране. Все это делается автоматически, вам не нужно писать код самостоятельно, чтобы выполнить эту работу.

Однако возникает проблема, когда этот код генерирует исключение, поэтому вам не удастся поймать такое исключение, так как ни один из ваших собственных кодов не был задействован. Другими словами, вам не нужно вводить свой собственный блок try. Последний бит вашего собственного программного кода, который был задействован, - это код, в котором запущен цикл сообщений. Вызов метода Application.Run(), обычно в Program.cs. Или вызов формы .ShowDialog(), если вы выведете диалог. Любой из этих методов запускает цикл сообщений. Помещение блока try вокруг вызова Application.Run() не является полезным, приложение завершит работу после исключения исключения.

Чтобы справиться с этой проблемой, код цикла сообщений Winforms содержит блок try вокруг кода, который отправляет событие. Его предложение catch отображает диалог, который вы упомянули, он реализуется классом ThreadExceptionDialog.

Подводя итог вашему вопросу: это предложение catch действительно мешает устранению неполадок с вашим кодом при отладке. Отладчик остановится только на исключении, если нет блока catch, который обрабатывает исключение. Но когда ваш код генерирует исключение, вы захотите узнать об этом при отладке. Ранее упомянутый код в цикле сообщения знает, подключен ли отладчик. Если это так, он отправляет события без блока try/catch. Теперь, когда ваш код генерирует исключение, для его обработки не существует предложение catch, и отладчик остановит программу, давая вам возможность узнать, что пошло не так.

Возможно, вы видите, почему ваша программа ведет себя так, как она делает. Когда вы отлаживаете, предложение catch в цикле сообщений отключается, предоставляя предложение catch в коде Form1 шанс поймать исключение. Когда вы этого не сделаете, предложение catch цикла сообщения обрабатывает исключение (путем отображения диалогового окна) и не позволяет исключению от разматывания кода Form1.

Вы можете запретить вообще использовать предложение catch цикла сообщения, вызывая метод Application.SetUnhandledExceptionMode(), передавая UnhandledExceptionMode.ThrowException. Сделайте это в методе Main() перед вызовом Application.Run(). Теперь ваша программа будет вести себя одинаково в любом случае.

Это вообще неплохая идея, предоставляющая пользователю возможность продолжить в диалоге исключений, является сомнительной функцией. Внесите обработчик событий для события AppDomain.UnhandledException в этом случае, так что, по крайней мере, некоторая диагностика для пользователя.