RunWorkerCompleted не выполняется в основном потоке пользовательского интерфейса? - PullRequest
2 голосов
/ 22 октября 2010

Я столкнулся со странным поведением BackgroundWorker или .NET Framework при сбросе исключения в событии RunWorkerCompleted.

Исключения, возникающие в фоновом потоке, передаются в RunWorkerCompleted. Там я делаю object temp = e.Result, чтобы отбросить исключение. Поскольку это должно происходить в основном потоке пользовательского интерфейса, я ожидаю, что исключение будет распространено до Application.Run(...), которое я окружил блоком try-catch.

При запуске приложения в Visual Studio это работает нормально. Тем не менее, когда я выполняю exe-файл вне Visual Studio, исключение не обрабатывается и вызывает сбой приложения.

Возможно ли, что RunWorkerCompleted событие не выполняется в основном потоке пользовательского интерфейса?

Эти сообщения не являются дубликатами, а скорее подтверждают, что моя ситуация странная:

Это очень упрощенный пример кода, который воспроизводит ошибку: создайте проект WinForms и добавьте:

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    try
    {
        Form form = new Form();

        form.Load += delegate
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += (sender, e) => 
            {
                // do nothing
            };
            bw.RunWorkerCompleted += (sender, e) =>
            {
                throw new Exception("Booo!");
            };
            bw.RunWorkerAsync();
        };

        Application.Run(form);
    }
    catch (Exception ex)
    {
        while (ex is TargetInvocationException && ex.InnerException != null)
            ex = ex.InnerException;

        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Тогда попробуйте:

  1. Запустите его в Visual Studio. Перед закрытием приложения вы получите дружественное окно с сообщением.
  2. Запустите скомпилированный исполняемый файл вне Visual Studio. Вы получите сообщение .NET Framework «необработанное исключение».

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

(Почему я хочу делать все эти вещи? Я отображаю заставку, и хотя некоторые запуска приложения выполняются в фоновом режиме, заставка отображает прогресс с помощью метки и индикатора выполнения. Итак, у меня есть создать и отобразить заставку, а затем захватить основной поток пользовательского интерфейса для запуска BackgroundWorker. В исходном коде он сообщает о ходе выполнения обратно в основной поток, который обновляет форму заставки. Если во время запуска возникает исключение, я хочу поймать это. В некоторых случаях они являются «бизнес-исключениями», которые имеют конкретное значение, например «вы не авторизованы для использования этого приложения». В этих случаях я отображаю дружественное окно сообщения, прежде чем позволить приложению умереть. Блок 1045 * для очистки ресурсов. Я не уверен, выполняет ли диалоговое окно «Необработанное исключение» .NET Framework блок finally, прежде чем убить приложение.)

1 Ответ

4 голосов
/ 22 октября 2010

В коде цикла сообщений внутри Application.Run () есть блок try / catch, который перехватывает необработанные исключения, вызванные обработчиком события.Предложение catch вызывает событие Application.ThreadException.Для этого события есть обработчик по умолчанию, он отображает ThreadExceptionDialog.Предоставление пользователю возможности игнорировать ошибку или отменить программу.

Это условие catch отключается при запуске вашей программы с помощью отладчика.Это позволяет легко отлаживать исключения.Если он отключен, CLR найдет предложение catch в вашем методе Main ().Чтобы отключить это поведение, добавьте следующую строку кода в начало вашего метода Main ():

  Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);

Теперь он будет вести себя так же, как в отладчике.Лучшей мышеловкой здесь является реализация обработчика событий для AppDomain.CurrentDomain.UnhandledException.Это перехватывает все необработанные исключения, включая те, которые возникают в рабочих потоках.И позволяет отлаживать необработанные исключения, отладчик останавливается на операторе throw.

...