Является асинхронной версией команды relaycommand, необходимой для правильной работы асинхронных методов

У меня есть следующий код, определенный в viewmodel. Я думаю, что SaveAsync типа Func<Task> преобразуется в Action, так как RelayCommand принимает Action, а не Func<Task>, но я не понимаю последствий этого.

1) Нужно ли заменить RelayCommand асинхронной версией (RelayCommandAsync)? 2) Что именно делает текущий код в отношении асинхронности? 3) Что делать, если что-нибудь может/нужно изменить, чтобы улучшить/исправить?

private ICommand _saveCommand;
public ICommand SaveCommand
{
    get { return _saveCommand ?? (_saveCommand = new RelayCommand(async () => await SaveAsync(), CanSave)); }
}

public bool CanSave()
{
    return !IsBusy;
}

private async Task SaveAsync()
{
    IsBusy = true;

    try
    {
        await _service.SaveAsync(SomeProperty); 
    }
    catch ( ServiceException ex )
    {
        Message = "Oops. " + ex.ToString();
    }
    finally
    {
        IsBusy = false;
    }
}

Спасибо!

РЕДАКТИРОВАТЬ: После некоторых экспериментов выяснилось, что сам метод async работает. И не имеет значения, включен ли асинхронный/ожидаемый в лямбда, и не был ли метод определен как async Task или async void.

Однако, что не работает правильно, это предикатная функция canExecute, которая автоматически включает/отключает привязку элемента управления к команде. Случается, что кнопка корректно отключена, когда выполняется метод async, но после этого она не включена. Мне нужно щелкнуть где-нибудь в окне один раз, а затем снова включить.

Итак, кажется, что для полной функциональности требуется асинхронная версия RelayCommand, т.е. canExecute может правильно выполнить свою работу.

Ответ 1

1) Нужно ли заменять RelayCommand асинхронной версией (RelayCommandAsync)?

Это не обязательно, но вы должны это рассмотреть.

2) Что именно делает текущий код для асинхронности?

Создает lambda async void. Это проблематично, потому что async void не особенно красиво обрабатывает исключения. Если вы используете RelayCommand с асинхронным кодом, то вы определенно захотите использовать try/catch как тот, что в вашем коде.

3) Что делать, если я могу изменить/изменить его/изменить?

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

Нет стандартного шаблона (пока); Я излагаю несколько разных подходов в статье MSDN. Лично, по крайней мере, я определяю IAsyncCommand в своих приложениях, которые я выставляю из своих виртуальных машин (это сложно для unit test async ICommand).

Однако, что не работает корректно, это функция прецедента canExecute, которая автоматически включает/отключает привязку управления к команде.

Предполагая, что RelayCommand.CanExecuteChanged делегирует CommandManager, вы можете просто вызвать CommandManager.InvalidateRequerySuggested после установки IsBusy.

Ответ 2

Нужно ли заменять RelayCommand асинхронной версией (RelayCommandAsync)?

Нет, RelayCommand будет работать по желанию.

Что именно делает текущий код для асинхронности?

Случается, что когда во время компиляции выполняется перегрузка, он выбирает перегрузку, которая принимает Action, что означает, что ваш метод переводится в async void, и именно поэтому ваш код компилируется.

3) Что делать, если я могу изменить/изменить его/изменить?

Существуют реализации команд async delegate. Здесь вы можете найти здесь. Важно отметить обработку исключений. В случае необлученного исключения внутри async ICommand, связанного с элементом управления WPF, исключение будет распространяться на связующее и исчезнуть без каких-либо ошибок и не замечено.

Ответ 3

Я не думаю, что вам нужна асинхронная версия команды relay. Ваша реализация выглядит нормально. Это работает?

Если вы хотите проверить, работает ли тело async, добавьте wait.delay(20000) и посмотрите, не работает ли пользовательский интерфейс во время выполнения команды.