Значение объявления обработчика события 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
, который накачивал событие в первую очередь.