Почему исключения не распространяются WPF Dispatcher.Invoke?

Вот мой гипотетический пример. У меня очень простое окно 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

ОБНОВЛЕНО: Чтобы наблюдать за исключением в другом потоке, вы хотите использовать 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 под ним и является новым способом записи повторно используемых компонентов, которым требуется такая синхронизация.