异步执行ICommand合理吗?

Async implementation of ICommand reasonable?

我有 async 接口的 ICommand 实现,当然必须实现方法 void Execute(object parameter)

实际的实现是异步的,如下所示:

public async void Execute(object parameter)
{
    await ExecuteAsync((T)parameter);
}

然后ExecuteAsync方法定义如下:

private readonly Func<T, Task> execute;
private bool isExecuting;

public async Task ExecuteAsync(T parameter)
{
    try
    {
        isExecuting = true;
        InvokeCanExecuteChanged();
        await execute(parameter);
    }
    finally
    {
        isExecuting = false;
        InvokeCanExecuteChanged();
    }
}

现在我知道除了 EventHandlers void 应该避免作为异步方法的 return 类型。然而 ICommand 或多或少是一个包装器,可以清楚地将视图模型与实际的事件处理程序分开。那么我可以接受这个实现吗?还是 运行 会遇到问题?

特别是我想知道我是否可以安全地使用执行命令并依赖任务在处理程序之前完成,或者处理程序是否会完成而不考虑任务的状态?

关于 async + void 的最大问题是,与普通 void 不同,调用站点上的任何后续代码都将在 async void 方法中的代码实际完成之前执行。你必须 100% 意识到这一点。

此行为是为什么与异步任务相比,async void 在 API 级别上并不常见。事实上,这就是为什么我在公司内部为任何使用 async void 写了一个编译器错误 - 并非所有开发人员都知道这一点,如果您希望该 void 方法的内容在调用站点代码继续之前完成,它可能会引入潜在的错误。

因此,如果您的命令提供 public async Task ExecuteAsync 版本的命令,那么您的命令就没问题。

通过此示例亲眼看看:

public class SampleCommand<T> : ICommand where T : class 
{
    /// <inheritdoc />
    public SampleCommand(Func<T, Task> execute)
    {
        this.execute = execute;
    }

    /// <inheritdoc />
    public bool CanExecute(object parameter)
    {
        return !isExecuting;
    }

    /// <inheritdoc />
    public async void Execute(object parameter)
    {
        await ExecuteAsync(parameter as T);
    }

    /// <inheritdoc />
    public event EventHandler CanExecuteChanged;

    private readonly Func<T, Task> execute;
    private bool isExecuting;

    public async Task ExecuteAsync(T parameter)
    {
        try
        {
            isExecuting = true;
            InvokeCanExecuteChanged();
            await execute(parameter);
        }
        finally
        {
            isExecuting = false;
            InvokeCanExecuteChanged();
        }
    }

    private void InvokeCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

public class SampleViewModel
{
    public SampleCommand<object> TaskedCommand { get; set; }

    public SampleViewModel()
    {
        TaskedCommand = new SampleCommand<object>(TaskExecutionAsync);

        RunSomeMoreInitialization();
        RunSomeMoreInitialization2();
    }

    private async void RunSomeMoreInitialization()
    {
        /*
         * wpf usually calls it this way 
         * if a user calls this he might not be aware of the different behavior of this void method 
         */
        TaskedCommand.Execute(null);
        await Task.Delay(250);
        Debug.WriteLine("more code executed");

        /* 
         * this will print 
         * 
         * more code executed
         * command invoked
         */
    }

    private async void RunSomeMoreInitialization2()
    {
        // user might manually call it this way.
        await TaskedCommand.ExecuteAsync(null);
        await Task.Delay(250);
        Debug.WriteLine("more code executed");
        /* 
         * this will print 
         * 
         * command invoked
         * more code executed
         */
    }

    private Task TaskExecutionAsync(object o)
    {
        Task.Delay(500);
        Debug.WriteLine("command invoked");

        return Task.CompletedTask;
    }
}