Почему исключения не распространяются WPF Dispatcher.Invoke? - PullRequest
14 голосов
/ 25 июня 2010

Вот мой гипотетический пример. У меня очень простое окно WPF с одной кнопкой. Событие Button.Click имеет обработчик, который выглядит следующим образом.

Action doit = () =>
{
    Action error = () => { throw new InvalidOperationException("test"); };

    try {
        this.Dispatcher.Invoke(error, DispatcherPriority.Normal);
    } catch (Exception ex) {
        System.Diagnostics.Trace.WriteLine(ex);
        throw;
    }
};
doit.BeginInvoke(null, null);

Я ожидаю, что исключение будет перехвачено и записано вызовом Trace.WriteLine. Вместо этого никакое исключение не перехватывается, и приложение разрывается.

Кто-нибудь знает возможное объяснение этого? И какой обходной путь вы предлагаете, чтобы перехватить исключения, выданные делегатом, вызванным Dispatcher.Invoke?

Обновление 1 : я вставил throw в код обработки исключений. Я не хочу игнорировать это исключение. Суть моего вопроса в том, чтобы правильно с ним справиться. Проблема в том, что код обработки исключений никогда не выполняется.

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

Обновление 2 : рассмотрите этот аналогичный пример. Вместо окна WPF у меня есть окно Windows Forms. У него есть кнопка с почти таким же обработчиком. Разница лишь в коде вызова. Это так.

this.Invoke(error);

В Windows Forms выполняется код обработки исключений. Почему разница?

1 Ответ

6 голосов
/ 25 июня 2010

ОБНОВЛЕНО: Чтобы наблюдать исключение в другом потоке, вы хотите использовать Task, поставить его в очередь Dispatcher (используя TaskScheduler.FromCurrentSynchronizationContext), и подожди, как таковой:

var ui = TaskScheduler.FromCurrentSynchronizationContext();
Action doit = () => 
{ 
    var error = Task.Factory.StartNew(
        () => { throw new InvalidOperationException("test"); },
        CancellationToken.None,
        TaskCreationOptions.None,
        ui); 

    try { 
        error.Wait(); 
    } catch (Exception ex) { 
        System.Diagnostics.Trace.WriteLine(ex); 
    } 
}; 
doit.BeginInvoke(null, null); 

ОБНОВЛЕНО (снова): Поскольку ваша цель - компонент многократного использования, я рекомендую перейти на интерфейс на основе Task или что-то еще на основе SynchronizationContext, такое как событие на основе асинхронного шаблона вместо того, чтобы основывать компонент на Dispatcher или ISynchronizeInvoke.

Компоненты

Dispatcher работают только на WPF / Silverlight; ISynchronizeInvoke компоненты работают только в Windows Forms. Компоненты на основе SynchronizationContext будут прозрачно работать с WPF или Windows Forms и (с немного большей работой) ASP.NET, консольными приложениями, службами Windows и т. Д.

Асинхронный шаблон на основе событий - это старый рекомендуемый способ записи SynchronizationContext компонентов; он все еще существует для кода .NET 3.5-эры. Если вы используете .NET 4, библиотека параллельных задач гораздо более гибкая, чистая и мощная. TaskScheduler.FromCurrentSynchronizationContext использует SynchronizationContext внизу и является новым способом написания повторно используемых компонентов, которые нуждаются в таком виде синхронизации.

...