Значение объявления обработчика события WPF как "async" в С# 5

Представьте, что обработчик событий с кодом для кода WPF:

<Button Click="OnButtonClick" />

В С# 4 вы объявите своего обработчика как:

private void OnButtonClick(object sender, RoutedEventArgs e) { ... }

В С# 5 вы можете объявить обработчик async

private async void OnButtonClick(object sender, RoutedEventArgs e) { ... }

Итак, что делает WPF с этим? Несколько минут поиска ничего не изменили.

Кажется, что возможно выполнять обновления пользовательского интерфейса после операторов await. Означает ли это, что задача продолжается в потоке диспетчера?

Если значение Task вызвало ошибку, оно было бы поднято через WPF Dispatcher или только через TaskScheduler?

Есть ли другие интересные аспекты, которые могут быть приятными для понимания?

Ответ 1

Вы можете найти мой асинхронный/ждущий ввод.

Метод async переписывается компилятором для поддержки оператора await. Каждый метод async запускается синхронно (в этом случае в потоке пользовательского интерфейса), пока не будет await некоторая операция (которая еще не завершена).

По умолчанию контекст сохраняется, и когда операция завершается, остальная часть метода планируется выполнить в этом контексте. "Контекст" здесь SynchronizationContext.Current, если он не равен null, и в этом случае это TaskScheduler.Current. Как отметил Дрю, WPF предоставляет DispatcherSynchronizationContext, который привязан к WPF Dispatcher.

Что касается обработки ошибок:

Когда вы await a Task внутри обработчика событий WPF async void, обработка ошибок выглядит следующим образом:

  • Task завершается с ошибкой. Исключение завершается в AggregateException, как и все ошибки Task.
  • Оператор await видит, что Task завершен с ошибкой. Он разворачивает исходное исключение и перебрасывает его, сохраняя исходную трассировку стека.
  • Конструктор методов async void ловит исключение из метода async void и передает его в SynchronizationContext, который был активным, когда начал запускать метод async void (в данном случае тот же контекст WPF).
  • Исключение возникает (с исходной трассировкой стека и без какой-либо досадной упаковки AggregateException) на Dispatcher.

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

Ответ 2

Частичный ответ. Из MSDN:

Асинхронный метод, который имеет тип возвращаемого типа void, не может быть ожидаемым, а вызывающий метод void-return не может поймать никаких исключений, которые вызывает метод.

Таким образом, любые ошибки будут доступны только через TaskScheduler.

Кроме того, нет ничего специфичного для XAML, связанного с регистрацией обработчика событий. Это можно было бы сделать в коде:

this.button.Click += OnButtonClick;

Или даже как асинхронная лямбда:

this.button.Click += async (s,e) => { ... };

Что касается безопасности обновлений пользовательского интерфейса после await, кажется, что продолжение выполняется в SynchronisationContext.Current, которое устанавливается за нить. В WPF это DispatcherSynchronisationContext, который связан с WPF Dispatcher, который накачивал событие в первую очередь.