У меня есть приложение WPF, основанное на архитектуре MVVM. Я реализую общий и широко используемый интерфейс INotifyPropertyChanged в своих ViewModels, потому что мне нужно реагировать на взаимодействие с пользователем.
Но как мне выполнить асинхронное действие (например, загрузить некоторые данные) из синхронного обработчика событий PropertyChanged без, используя async void «хаки»?
Заранее спасибо!
РЕДАКТИРОВАТЬ
Основная причина, по которой мне нужно избегать async void, заключается в том, что я работаю в тестовой среде. Асинхронные методы void не тестируются :(
В любом случае загрузка данных в свойство обычно считается плохой практикой. Обычно это один из критериев создания метода, а не использования свойства.





Вы не можете. INotifyPropertyChanged не поддерживает асинхронные вызовы. Вам нужно сделать взлом или переосмыслить свою стратегию.
INotifyPropertyChanged не предназначен для асинхронных действий. Его цель — позволить классу уведомлять пользовательский интерфейс об изменении его данных. Пользовательский интерфейс работает в выделенном потоке, поэтому следует избегать операций с несколькими потоками.
Вы должны использовать «ужасный» подход async void.
Вы также можете использовать Dispatcher.BeingInvoke (async () => { … await …} ), но это будет то же самое, что и async void.
На самом деле, это не про async void.
Обычно вы хотите запустить асинхронную операцию и позволить вашему установщику свойства вернуться. Пример фрагмента:
private string carManufacturerFilter;
public string СarManufacturerFilter
{
get { return carManufacturerFilter; }
set
{
if (carManufacturerFilter != value)
{
carManufacturerFilter = value;
OnPropertyChanged();
// fire async operation and forget about it here;
// you don't need it to complete right now;
var _ = RefreshCarsListAsync();
}
}
}
private async Task RefreshCarsListAsync()
{
// call some data service
var cars = await someDataService.GetCarsAsync(carManufacturerFilter)
.ConfigureAwait(false);
// ...
}
Обратите внимание, что здесь есть что добавить:
P.S. Я настоятельно рекомендую вам взглянуть на Реактивный пользовательский интерфейс.
Не будет ли он жаловаться на то, что не ожидает ожидаемого метода?
Если вы о предупреждении компилятора, то присваивание (var _ = ) подавляет его. IRL этот код будет завернут во что-то вроде AsyncCommand и т. д. Это пример, который предназначен для того, чтобы дать основную идею OP. Это не готовый к производству код.
Как можно выполнить модульное тестирование с помощью этого кода?
@Dennis Как мне что-то утверждать, если задачу нельзя ожидать при использовании async void?
Здесь нет асинхронной пустоты. Вот обычный асинхронный метод, который возвращает Task. Но в любом случае вы не должны ждать этого, потому что это приведет к зависанию пользовательского интерфейса.
Причина, по которой поддерживается async void, заключается в том, чтобы разрешить использование await в обработчиках событий, которые обычно недействительны.
Если вы хотите, чтобы его можно было протестировать, напишите весь код в другом методе async Task и пусть обработчик событий вызывает его напрямую. Проверьте этот метод в своих тестах.
void OnPropertyChanged(PropertyChangedEventArgs e)
{
OnPropertyChangedAsync(e)
}
// Test this method
async Task OnPropertyChangedAsync(PropertyChangedEventArgs e)
{
...
}
Использование
async voidс обработчиком событий — это не хак. Это предпочтительное решение. Обработчики событий по своей природе работают и забывают, поэтому не следует ожидать, что обработчик событий вернет какое-либо значение.