ICommand的异步实现是否合理?

时间:2017-12-06 10:14:38

标签: c# wpf asynchronous async-await icommand

我已经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之外应该避免作为异步方法的返回类型。但是ICommand或多或少是一个将视图模型与实际事件处理程序分开的包装器。所以我对这个实现很好还是会遇到问题?

特别是我想知道我是否可以安全地使用execute命令并依赖Task在处理程序之前完成,或者处理程序是否完成而不管Task的状态如何?

1 个答案:

答案 0 :(得分:2)

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

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

因此,如果命令提供了命令的公共异步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;
    }
}