Можете ли вы поймать исключение изнутри Application.DoEvents ()? - PullRequest
2 голосов
/ 17 марта 2009

Я столкнулся со странной разницей между программой, запущенной в VS2005 и выполняющей исполняемый файл напрямую. По сути, когда исключение выдается в методе внутри вызова Application.DoEvents(), исключение может быть перехвачено при запуске в Visual Studio. При запуске скомпилированного исполняемого файла исключение не перехватывается и программа вылетает.

Вот простой код, демонстрирующий проблему. Предположим, стандартный шаблон winforms, две кнопки и метка.

Для запуска нажмите кнопку «Пуск», чтобы начать отсчет 10 секунд. До истечения 10 секунд нажмите кнопку отмены. и исключение будет брошено внутри DoEvents(). Исключение следует поймать. Это происходит только при работе внутри Visual Studio.

    private void StartButton_Click(object sender, EventArgs e) {
        DateTime start = DateTime.Now;

        try {
            while (DateTime.Now - start < new TimeSpan(0, 0, 10)) {
                this.StatusLabel.Text = DateTime.Now.ToLongTimeString();
                Application.DoEvents();
            }

            MessageBox.Show("Completed with no interuption.");
        } catch (Exception) {
            MessageBox.Show("User aborted.");                
        }
    }

    private void ButtonAbort_Click(object sender, EventArgs e) {
        throw new Exception("aborted");
    }

Я хочу быть в состоянии поймать эти исключения. Есть ли способ заставить его работать?

Обновление:

Я готов рассмотреть иные подходы, кроме тех, которые вызывают головную боль у новичка DoEvents(). Но я не нашел тот, который, кажется, работает лучше. Мой сценарий состоит в том, что у меня есть длительный цикл, который контролирует некоторые научные инструменты, и часто приходится ждать стабилизации температуры или чего-то еще. Я хочу дать своим пользователям возможность прервать процесс, поэтому у меня есть кнопка отмены, которая просто генерирует пользовательское исключение, которое я собираюсь отследить на сайте, где процесс изначально запускается. Это казалось идеальным решением. За исключением того факта, что по какой-то причине он не работает.

Если не получается заставить это работать, есть ли лучший подход?

Обновление 2:

Когда я добавляю это как первую строку Main (), это заставляет его работать как исполняемый файл, но не в VS, поэтому ситуация меняется. Сумасшедшая вещь состоит в том, что это, кажется, бездействие. Я могу понять, как это что-то делает.

Application.ThreadException += delegate(
        object sender, 
        System.Threading.ThreadExceptionEventArgs e
    ) 
    { throw e.Exception; };

Это безумие.

Ответы [ 4 ]

6 голосов
/ 17 марта 2009

Вы действительно должны использовать DoEvents? Это приводит к повторному входу, который может быть очень трудно отладить.

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

РЕДАКТИРОВАТЬ: Да, безусловно, есть лучший способ сделать это. Выполните свою долгосрочную задачу в другом потоке. Поток пользовательского интерфейса должен только выполнять операции пользовательского интерфейса. Сделайте так, чтобы ваша кнопка «abort» установила флажок, который регулярно проверяет долгосрочное задание. См. Мою страницу с потоками WinForms , где приведен пример работы с потоками WinForms, и страница волатильности *1013* для примера того, как один поток устанавливает флаг для отслеживания другим потоком.

РЕДАКТИРОВАТЬ: Я только что вспомнил, что BackgroundWorker (который не рассматривается в моей статье - она ​​была написана до .NET 2.0) имеет CancelAsync метод - в основном это (используется с CancellationPending и WorkerSupportsCancellation, которые в основном имеют дело с «иметь флаг для отмены» для вас. Просто отметьте CancellationPending из рабочий поток и вызовите CancelAsync из обработчика нажатия кнопки прерывания.

2 голосов
/ 17 марта 2009

Поскольку DoEvents вызывает прокачку основного цикла сообщений, это может привести к повторному входу (как указано в MSDN ), что сделает поведение приложения непредсказуемым. *

Я бы посоветовал вам перенести вашу работу (в ожидании стабилизации температуры) в поток и использовать какой-то сигнал, чтобы сообщить вашему пользовательскому интерфейсу, когда ожидание закончится. Это позволит вашему пользовательскому интерфейсу оставаться отзывчивым без использования Application.DoEvents .

1 голос
/ 01 июля 2010

Не пытается вспомнить старые новости, а потому что я боролся с этой же проблемой и не нашел решения. Если вы работаете в фоновом режиме и ваши пользователи вернулись в основной поток приложения, работая и занимаясь другими делами, и они хотят запустить ту же функцию, которая лежит в фоновом режиме, Application.DoEvents () кажется лучшим способом, и вы все еще можете используй это. Проблема заключается в том, что у вас есть код в событии RunWorkerCompleted. Когда вы отменяете самого работника с помощью CancelAsync, все, что существует в RunWorkerCompleted, будет выполнено. Проблема в том, что обычно все, что находится в вашем RunWorkerCompleted, имеет доступ к GUI, и вы не можете получить доступ к GUI во время выполнения Application.DoEvents (), удаление кода из RunWorkerCompmleted и включение его в функцию ReportProgress более приемлемо. Однако вам нужно бросить события проверки прямо перед функцией reportprogress, чтобы убедиться, что основной поток не запрашивает другой запуск. Так что-то вроде этого прямо перед отчетом о прогрессе:

if (backgroundWorker1.CancellationPending == true){
     e.Cancel = true;
     return;
}
backgroundWorker1.ReportProgress(0);

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

if (backgroundWorker1.IsBusy == true){
     backgroundWorker1.CancelAsync();
     While(backgroundWorker1.CancellationPending == true){
          Application.DoEvents();
     }
}

Хотя это, по-видимому, плохая практика, она позволяет избавиться от исключения. Я искал все это до тех пор, пока не начал изменять проект, над которым я работал, и увидел, что единственная причина, по которой он ошибался, заключалась в том, что он одновременно обращался к основному потоку, Я ВЕРИМ. Наверное, за это будет разгораться, но ответ есть.

1 голос
/ 17 марта 2009

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

Чтобы сделать это первым, вам нужно установить Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); в самом начале приложения, до создания каких-либо элементов управления. Затем добавьте обработчик события ThreadException.

Если это не сработает, я бы порекомендовал использовать BackgroundWorker, что означает, что вам не нужно использовать DoEvents, и это также будет лучшим решением.

РЕДАКТИРОВАТЬ: в вашем примере обработчик ThreadException вы бросаете еще одно исключение. Идея заключалась в том, чтобы вы поместили туда свой код обработки исключений, а не создавали другое исключение.

Однако я все равно призываю вас использовать BackgroundWorker для выполнения кода вашего цикла.

...