是否需要RelayCommand的异步版本才能正确运行异步方法?

18

我在一个视图模型中定义了下面的代码。我认为类型为 Func<Task> 的 SaveAsync 正在被转换为 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;
    }
}

谢谢!

编辑:经过一些尝试,似乎异步方法本身是正常工作的。无论lambda是否包含async/await以及方法是否定义为async Taskasync void都没有区别。

然而,不能正确工作的是canExecute谓词功能,它自动启用/禁用与命令绑定的控件。在异步方法运行时,按钮被正确地禁用了,但之后它没有再次启用。我必须在窗口某个地方点击一次,然后它才能再次启用。

因此,似乎需要一个异步版本的RelayCommand才能实现完整的功能,即使得canExecute可以正确地执行其功能。


IsBusy是否实现了iNotifyPropertyChanged,如果是,你的vm是否执行了它并且属性调用了你的方法来触发事件?或者这是一个依赖属性吗?如果是这样的话,你必须在UI线程上进行更改。这只是一个typical error dealing with this。 - Stígandr
4个回答

24

1)RelayCommand 需要被异步版本( RelayCommandAsync) 替换吗?

不是必须的,但你应该考虑一下。

2)关于异步,当前代码到底在做什么?

它创建了一个 async void lambda。这是有问题的,因为 async void 没有处理异常的特别好方法。如果你使用带有异步代码的 RelayCommand ,那么你肯定需要使用像你代码中的 try/catch 这样的语句块。

3)我能/应该改变什么来改进/修正它?

如果这是你的代码中唯一的异步命令,我认为可以接受。然而,如果你发现应用程序中有几个具有类似语义的异步命令,那么你应该考虑编写一个 RelayCommandAsync

目前没有标准的模式;我在 MSDN文章中概述了几种不同的方法。个人而言,至少在我的应用程序中定义了一个 IAsyncCommand,并从我的 VM 中公开它(异步 ICommand 很难进行单元测试)。

然而,不能正常工作的是 canExecute 谓词功能,它会自动启用/禁用绑定到命令的控件。

假设 RelayCommand.CanExecuteChanged 委托给了 CommandManager,那么你可以在设置 IsBusy 后调用 CommandManager.InvalidateRequerySuggested


5

RelayCommand是否需要被异步版本(RelayCommandAsync)替换?

不需要,RelayCommand在这里可以正常工作。

关于异步,当前代码具体是做什么的?

发生的是,当编译时过载解析启动时,选择接受Action参数的重载,这意味着您的方法被翻译成了async void,这就是为什么您的代码可以编译的原因。

3) 如果有什么需要改进/更正的地方,我该怎么办?

有一些异步委托命令的实现。您可以在这里找到其中之一。一个重要的事项是异常处理。在绑定到WPF控件的异步ICommand中发生未处理的异常,异常会传播到绑定器并且不被处理或注意到。


2

我认为你不需要一个异步版本的relay命令。你的实现看起来很好。它能工作吗?

如果你想测试body是否在异步运行,可以添加await task.delay(20000),看看当命令正在运行时UI是否仍然响应。


1
不。在绑定上设置IsAsync。就像这样:

<Button Command="{Binding DoCommand, IsAsync=True}" />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接