Предотвращение появления необработанного диалога исключений - PullRequest
3 голосов
/ 09 июля 2009

Сначала позвольте мне сказать, что я полностью прочитал эту полезную статью и использую класс SafeThread из CodeProject. Я получаю одинаковый результат при использовании Thread или SafeThread.

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

Когда я запускаю это под VS2008, я вижу «Исключение в DoRun ()».

Когда я запускаю за пределами VS2008, я получаю диалоговое окно «Необработанное исключение произошло в вашем приложении. Если вы нажмете продолжить, приложение ....»

Я попытался установить legacyUnhandledExceptionPolicy в app.config как в 1, так и в 0.

Что мне нужно сделать, чтобы перехватить исключение, выброшенное во второй форме, когда не работает под VS2008?

Вот моя Program.cs

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.ThreadException += new ThreadExceptionEventHandler    (Application_ThreadException);
            Application.SetUnhandledExceptionMode    (UnhandledExceptionMode.CatchException);
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            try
            {
               Application.Run(new Form1());
            }
            catch(Exception ex)
            {
                MessageBox.Show("Main exception");
            }                
        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            MessageBox.Show("CurrentDomain_UnhandledException");
        }

        static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            MessageBox.Show("Application_ThreadException");
        }
    }

Вот форма 1:

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

    private void button1_Click(object sender, EventArgs e)
    {
        SafeThread t = new SafeThread(new SimpleDelegate(ThreadMain));
        try
        {
            t.ShouldReportThreadAbort = true;
            t.ThreadException += new ThreadThrewExceptionHandler(t_ThreadException);
            t.ThreadCompleted += new ThreadCompletedHandler(t_ThreadCompleted);
            t.Start();
        }
        catch(Exception ex)
        {
            MessageBox.Show(string.Format("Caught externally! {0}", ex.Message));

        }
    }

    void t_ThreadCompleted(SafeThread thrd, bool hadException, Exception ex)
    {
        MessageBox.Show("t_ThreadCompleted");
    }

    void t_ThreadException(SafeThread thrd, Exception ex)
    {
        MessageBox.Show(string.Format("Caught in safe thread! {0}", ex.Message));
    }

    void ThreadMain()
    {
        try
        {
            DoRun();
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("Caught! {0}", ex.Message));
        }
    }

    private void DoRun()
    {
        try
        {
            Form2 f = new Form2();
            f.Show();
            while (!f.IsClosed)
            {
                Thread.Sleep(1);
                Application.DoEvents();
            }
        }
        catch(Exception ex)
        {
            MessageBox.Show("Exception in DoRun()");
        }
    }
}

А вот форма2:

public partial class Form2 : Form
{
    public bool IsClosed { get; private set; }

    public Form2()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        throw new Exception("INTERNAL EXCEPTION");
    }

    protected override void OnClosed(EventArgs e)
    {
        IsClosed = true;
    }
}

Ответы [ 5 ]

3 голосов
/ 31 июля 2009

Если вы действительно хотите, чтобы ваша вторая форма открывалась в отдельном потоке пользовательского интерфейса (не как ShowDialog()), чтобы перехватить исключение и отправить его в метод Application_ThreadException, вам необходимо убедиться, что для второго потока также установлено значение CatchException и вам также необходимо подписаться на Application.ThreadException в этой теме . И то, и другое зависит от потока (и немного странно).

Вы можете установить «режим необработанного исключения» по умолчанию, вызвав:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

Это устанавливает режим всего приложения на CatchException для любого потока пользовательского интерфейса, который вы создаете. (Но этот вызов не будет выполнен при запуске в отладчике Visual Studio и некоторых других случаях.) Или ваш новый поток пользовательского интерфейса может установить свой собственный режим с помощью обычного вызова (аналогично передаче true для этой перегрузки).

В любом случае новый поток пользовательского интерфейса также должен подписаться на само событие Application.ThreadException, поскольку подписчик хранится в переменной [ThreadStatic].

Application.ThreadException += Program.Application_ThreadException;

Или он может использовать отдельный обработчик вместо маршрутизации на тот же, если это полезно.

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

Также см. Мой ответ на этот вопрос , чтобы узнать больше о причудах этого материала.

2 голосов
/ 09 июля 2009

1.) Я бы рекомендовал использовать BackgroundWorker вместо отдельных потоков, как это. Ваш работник будет перехватывать исключения и передавать их в качестве параметра полному обработчику.

2.) Я бы использовал ShowDialog () вместо Show () при отображении второй формы, это будет блокировать DoRun () при вызове этого метода, а исключения должны быть перехвачены окружающим try / catch (или BackgroundWorker если вы используете это вместо).

Я думаю, что проблема заключается в том, что, поскольку вы вызываете Show (), вы по сути отправляете этот вызов на Invoker, который в итоге оказывается в очереди в потоке пользовательского интерфейса. Поэтому, когда возникает исключение, нет ничего выше, чем callstack, чтобы его перехватить. Я полагаю, что вызов ShowDialog () исправит это (и также позволит вам отказаться от этого неприятного цикла).

Примерно так:

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

    private void button1_Click(object sender, EventArgs e)
    {
        // NOTE: I forget the event / method names, these are probably a little wrong.
        BackgroundWorker worker = new BackgroundWorker();
        worker.DoWork += (o, e) =>
        {
            Form2 f = new Form2();
            e.Result = f.ShowDialog();
        };
        worker.DoWorkComplete += (o, e) =>
        { 
            if(e.Error != null)
                MessageBox.Show(string.Format("Caught Error: {0}", ex.Message));

            // else success!
            // use e.Result to figure out the dialog closed result.
        };

        worker.DoWorkAsync();
    }
}

На самом деле, теперь, когда я думаю об этом, это немного странно - открывать диалог из фонового потока, но я думаю, что это все равно будет работать.

1 голос
/ 09 июля 2009

Вместо этой строки:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

Вам нужно это:

#if DEBUG
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
#else
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
#endif

Таким образом, когда вы запускаете программу в Visual Studio в режиме отладки, Visual Studio будет перехватывать исключения, когда они случаются, так что вы можете отлаживать их в момент их возникновения. Когда вы запускаете программу в режиме выпуска, исключения будут перехвачены обработчиком для Application.ThreadException или обработчиком для AppDomain.

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

0 голосов
/ 09 июля 2009

Кажется, проблема вызвана обработчиком события кнопки. Если вы выбросите исключение в DoRun() - возможно, после Form.Show() - исключение будет считаться ожидаемым независимо от того, работаете ли вы в Visual Studio или нет.

Интересно, что поведение зависит от того, подключен ли отладчик к процессу или нет. Запуск за пределами Visual Studio и последующее присоединение отладчика предотвращают появление окна сообщения обратной связи, отсоединение вызывает его повторение. То же самое из Visual Studio - «Запуск без отладки» приводит к появлению окна сообщения обратной связи.

Итак, я быстро прошел по исходному коду фреймворка после того, как в обработчике событий кнопки возникла исключительная ситуация, и я грубо на нее посмотрел - насос сообщений, элементы управления и, вероятно, много другого кода делают там много всего. Поскольку WinForms являются просто оболочкой для собственных элементов управления, я предполагаю, что по какой-то причине исключение не возвращается в ту же точку или поток, в зависимости от того, подключен отладчик или нет - возможно, что-то идет не так, когда исключение передается через границы потока или процесса или что-то в этом роде.

0 голосов
/ 09 июля 2009

Вы пытались вставить блок try / catch в свой основной метод?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...