Как вызвать асинхронный метод из PropertyChanged?

У меня есть приложение WPF, основанное на архитектуре MVVM. Я реализую общий и широко используемый интерфейс INotifyPropertyChanged в своих ViewModels, потому что мне нужно реагировать на взаимодействие с пользователем.

Но как мне выполнить асинхронное действие (например, загрузить некоторые данные) из синхронного обработчика событий PropertyChanged без, используя async void «хаки»?

Заранее спасибо!

РЕДАКТИРОВАТЬ

Основная причина, по которой мне нужно избегать async void, заключается в том, что я работаю в тестовой среде. Асинхронные методы void не тестируются :(

Использование async void с обработчиком событий — это не хак. Это предпочтительное решение. Обработчики событий по своей природе работают и забывают, поэтому не следует ожидать, что обработчик событий вернет какое-либо значение.

FCin 15.02.2019 14:07

В любом случае загрузка данных в свойство обычно считается плохой практикой. Обычно это один из критериев создания метода, а не использования свойства.

Crowcoder 15.02.2019 14:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
4 176
3

Ответы 3

Вы не можете. 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. Я настоятельно рекомендую вам взглянуть на Реактивный пользовательский интерфейс.

Не будет ли он жаловаться на то, что не ожидает ожидаемого метода?

FCin 15.02.2019 14:41

Если вы о предупреждении компилятора, то присваивание (var _ = ) подавляет его. IRL этот код будет завернут во что-то вроде AsyncCommand и т. д. Это пример, который предназначен для того, чтобы дать основную идею OP. Это не готовый к производству код.

Dennis 15.02.2019 14:44

Как можно выполнить модульное тестирование с помощью этого кода?

WΩLLE - ˈvɔlə 15.02.2019 14:54

@Dennis Как мне что-то утверждать, если задачу нельзя ожидать при использовании async void?

WΩLLE - ˈvɔlə 15.02.2019 15:44

Здесь нет асинхронной пустоты. Вот обычный асинхронный метод, который возвращает Task. Но в любом случае вы не должны ждать этого, потому что это приведет к зависанию пользовательского интерфейса.

Dennis 15.02.2019 15:53

Причина, по которой поддерживается async void, заключается в том, чтобы разрешить использование await в обработчиках событий, которые обычно недействительны. Если вы хотите, чтобы его можно было протестировать, напишите весь код в другом методе async Task и пусть обработчик событий вызывает его напрямую. Проверьте этот метод в своих тестах.

void OnPropertyChanged(PropertyChangedEventArgs e)
{
    OnPropertyChangedAsync(e)
}

// Test this method
async Task OnPropertyChangedAsync(PropertyChangedEventArgs e)
{
    ...
}

Другие вопросы по теме