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

本文关键字:运行 异步方法 版本 relaycommand 异步 是否 | 更新日期: 2023-09-27 18:16:02

我在视图模型中定义了以下代码。我认为类型Func<Task>的SaveAsync正在转换为动作,因为RelayCommand需要一个动作而不是一个Func<Task>,但我不清楚它的含义。

1) RelayCommand是否需要替换为异步版本(RelayCommandAsync)?2)关于异步,当前代码究竟在做什么?如果我能/应该做些什么来改进/纠正它呢?

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;
    }
}

谢谢!

EDIT:经过一些实验后,似乎async方法本身可以正常工作。并且,在lambda中是否包含async/await以及该方法是否定义为async Taskasync void都没有区别。

然而,不能正常工作的是canExecute谓词功能,该功能自动启用/禁用控件绑定到命令。发生的情况是,当async方法运行时,按钮被正确禁用,但之后没有启用。我必须点击窗口上的某个地方一次,然后它再次启用。

所以看起来RelayCommand的异步版本需要完整的功能,也就是说,所以canExecute可以正确地做它的事情。

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

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

不必如此,但你应该考虑一下。

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

它正在创建一个async void lambda。这是有问题的,因为async void不能很好地处理异常。如果你在异步代码中使用RelayCommand,那么你肯定会想要使用try/catch,就像你的代码中那样。

3)如果我可以/应该改变什么来改进/纠正它?

如果这是你代码中唯一的async命令,我会说它很好。但是,如果您发现应用程序中有几个具有类似语义的async命令,那么您应该考虑编写一个RelayCommandAsync

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

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

假设RelayCommand.CanExecuteChanged委托给CommandManager,则设置IsBusy后,直接调用CommandManager.InvalidateRequerySuggested即可。

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

不,RelayCommand将在这里工作。

关于异步,当前代码究竟在做什么?

发生的事情是,当重载解析在编译时开始时,它选择接受Action的重载,这意味着你的方法被翻译成async void,这就是为什么你的代码编译。

3)如果我可以/应该改变什么来改进/纠正它?

有async委托命令的实现。你可以在这里找到一个需要注意的一个重要事项是异常处理。在绑定到WPF控件的异步ICommand中出现未处理异常的情况下,该异常将传播到绑定器,并且未被处理且不被注意。

我认为您不需要中继命令的异步版本。您的实现看起来不错。它有用吗?

如果你想测试主体是否在异步运行,添加一个await task.delay(20000),看看当命令运行时UI是否保持响应

No。在绑定上设置IsAsync。像这样:

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