繁体   English   中英

带有可选 canExecute 的异步 ICommand 实现

[英]Asyncronous ICommand implementation with optional canExecute

我有以下 ICommand 实现,效果很好,但我想扩展它,以便我可以传递外部 canExecute 参数

public class AsyncRelayCommand : ICommand
    {
        private readonly Func<object, Task> callback;
        private readonly Action<Exception> onException;
        private bool isExecuting;

        public bool IsExecuting
        {
            get => isExecuting;
            set
            {
                isExecuting = value;
                CanExecuteChanged?.Invoke(this, new EventArgs());
            }
        }
        public event EventHandler CanExecuteChanged;

        public AsyncRelayCommand(Func<object, Task> callback, Action<Exception> onException = null)
        {
            this.callback = callback;
            this.onException = onException;
        }

        public bool CanExecute(object parameter) => !IsExecuting;

        public async void Execute(object parameter)
        {
            IsExecuting = true;
            try
            {
                await callback(parameter);
            }
            catch (Exception e)
            {
                onException?.Invoke(e);
            }

            IsExecuting = false;
        }
    }

能否以某种方式扩展此实现,以便当调用者的 CanExecute() 更改时, Execute1AsyncCommand 和 Execute2AsyncCommand 都会承认这一点? 这是我的来电类:

public class Caller : ObservableObject
{
public ObservableTask Execute1Task { get; } = new ObservableTask();
public ObservableTask Execute2Task { get; } = new ObservableTask();

public ICommand Execute1AsyncCommand { get; }
public ICommand Execute2AsyncCommand { get; }

public Caller()
{
    Execute1AsyncCommand = new AsyncRelayCommand(Execute1Async);
    Execute2AsyncCommand = new AsyncRelayCommand(Execute2Async);
}

private bool CanExecute(object o)
{
    return Task1?.Running != true && Task2?.Running != true;
}

private async Task Execute1Async(object o)
{
    Task1.Running = true;
            
    try
    {
        await Task.Run(()=>Thread.Sleep(2000)).ConfigureAwait(true);
        Task1.RanToCompletion = true;
    }
    catch (Exception e)
    {
        Task1.Faulted = true;
    }
}
private async Task Execute2Async(object o)
{
    Task2.Running = true;

    try
    {
        await Task.Run(() => Thread.Sleep(2000)).ConfigureAwait(true);
        Task2.RanToCompletion = true;
    }
    catch (Exception e)
    {
        Task2.Faulted = true;
    }
}
}

在其他调用者中,我仍然希望能够使用AsyncRelayCommand() ,而只是callback是强制性的。 在这种情况下CanExecute应该从内部评估AsyncRelayCommand在我原来的执行。

为了完整起见,这是我的观点:

<StackPanel>
    <Button Content="Execute Task 1"
            Command="{Binding Execute1AsyncCommand}" />
    <Button Content="Execute Task 2"
            Command="{Binding Execute2AsyncCommand}" />
    <TextBlock Text="Task 1 running:" />
    <TextBlock Text="{Binding Task1.Running}" />
    <TextBlock Text="Task 2 running:" />
    <TextBlock Text="{Binding Task2.Running}" />
</StackPanel>

和 ObservableTask 类:

public class ObservableTask : ObservableObject
{
    private bool running;
    private bool ranToCompletion;
    private bool faulted;

    public Task Task { get; set; }

    public bool WaitingForActivation => !Running && !RanToCompletion && !Faulted;

    public bool Running
    {
        get => running;
        set
        {
            running = value;
            if (running)
            {
                RanToCompletion = false;
                Faulted = false;
            }
        }
    }

    public bool RanToCompletion
    {
        get => ranToCompletion;
        set
        {
            ranToCompletion = value;
            if (ranToCompletion)
            {
                Running = false;
            }
        }
    }

    public bool Faulted
    {
        get => faulted;
        set
        {
            faulted = value;
            if (faulted)
            {
                Running = false;
            }
        }
    }
}

我想要实现的是在用户按下一个按钮后,直到所有任务都完成为止。

解决方案

我最终得到了以下实现,到目前为止它按预期工作:

public class AsyncRelayCommand : ICommand
{
    private bool isExecuting;
    private readonly Func<object, Task> execute;
    private readonly Predicate<object> canExecute;
    private readonly Action<Exception, object> onException;

    private Dispatcher Dispatcher { get; }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public AsyncRelayCommand(Func<object, Task> execute, Predicate<object> canExecute = null, Action<Exception, object> onException = null)
    {
        this.execute = execute;
        this.canExecute = canExecute;
        this.onException = onException;
        Dispatcher = Application.Current.Dispatcher;
    }

    private void InvalidateRequerySuggested()
    {
        if (Dispatcher.CheckAccess())
            CommandManager.InvalidateRequerySuggested();
        else
            Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
    }

    public bool CanExecute(object parameter) => !isExecuting && (canExecute == null || canExecute(parameter));

    private async Task ExecuteAsync(object parameter)
    {
        if (CanExecute(parameter))
        {
            try
            {
                isExecuting = true;
                InvalidateRequerySuggested();
                await execute(parameter);
            }
            catch (Exception e)
            {
                onException?.Invoke(e, parameter);
            }
            finally
            {
                isExecuting = false;
                InvalidateRequerySuggested();
            }
        }
    }

    public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}

用法:

public class Caller: ObservableObject
{
    public ObservableTask Task1 { get; } = new ObservableTask();
    public ObservableTask Task2 { get; } = new ObservableTask();
    public ObservableTask Task3 { get; } = new ObservableTask();

    public ICommand Execute1AsyncCommand { get; }
    public ICommand Execute2AsyncCommand { get; }
    public ICommand Execute3AsyncCommand { get; }

    public Caller()
    {
        // Command with callers CanExecute method and error handled by callers method.
        Execute1AsyncCommand = new AsyncRelayCommand(Execute1Async, CanExecuteAsMethod, Execute1ErrorHandler);

        // Command with callers CanExecute parameter and error handled inside task therefore not needed.
        Execute2AsyncCommand = new AsyncRelayCommand(Execute2Async, _=>CanExecuteAsParam);

        // Some other, independent command.
        // Minimum example - CanExecute is evaluated inside command, error handled inside task.
        Execute3AsyncCommand = new AsyncRelayCommand(Execute3Async);
    }

    public bool CanExecuteAsParam => !(Task1.Running || Task2.Running);
    private bool CanExecuteAsMethod(object o)
    {
        return !(Task1.Running || Task2.Running);
    }

    private async Task Execute1Async(object o)
    {
        Task1.Running = true;
        await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
        Task1.RanToCompletion = true;
    }
    private void Execute1ErrorHandler(Exception e, object o)
    {
        Task1.Faulted = true;
    }

    private async Task Execute2Async(object o)
    {
        try
        {
            Task2.Running = true;
            await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
            Task2.RanToCompletion = true;
        }
        catch (Exception e)
        {
            Task2.Faulted = true;
        }
    }

    private async Task Execute3Async(object o)
    {
        try
        {
            Task3.Running = true;
            await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
            Task3.RanToCompletion = true;
        }
        catch (Exception e)
        {
            Task3.Faulted = true;
        }
    }
}

感谢大家的宝贵帮助!

我有一些现成的解决方案。

  • 常规同步委托,因此它可以代替简单的RelayCommand
  • 委托在池线程上执行。
  • 命令执行时CanExecutefalse ,因此会自动禁用控制。

执行

public interface IAsyncCommand : ICommand
{
    Task ExecuteAsync(object param);
}

public class AsyncRelayCommand : IAsyncCommand
{
    private bool _isExecuting;
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

    private Dispatcher Dispatcher { get; }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public AsyncRelayCommand(Action<object> execute, Predicate<object> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
        Dispatcher = Application.Current.Dispatcher;
    }

    private void InvalidateRequerySuggested()
    {
        if (Dispatcher.CheckAccess())
            CommandManager.InvalidateRequerySuggested();
        else
            Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
    }

    public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));

    public async Task ExecuteAsync(object parameter)
    {
        if (CanExecute(parameter))
        {
            try
            {
                _isExecuting = true;
                InvalidateRequerySuggested();
                await Task.Run(() => _execute(parameter));
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                _isExecuting = false;
                InvalidateRequerySuggested();
            }
        }
    }

    public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}

用法

private IAsyncCommand _myAsyncCommand;

public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(parameter =>
{
    Thread.Sleep(2000);
}));

注意:您无法处理ObservableCollection从非UI线程,作为解决办法,我建议这一个


异步委托版本

public class AsyncRelayCommand : IAsyncCommand
{
    private bool _isExecuting;
    private readonly Func<object, Task> _executeAsync;
    private readonly Predicate<object> _canExecute;

    private Dispatcher Dispatcher { get; }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public AsyncRelayCommand(Func<object, Task> executeAsync, Predicate<object> canExecute = null)
    {
        _executeAsync = executeAsync;
        _canExecute = canExecute;
        Dispatcher = Application.Current.Dispatcher;
    }

    private void InvalidateRequerySuggested()
    {
        if (Dispatcher.CheckAccess())
            CommandManager.InvalidateRequerySuggested();
        else
            Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
    }

    public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));

    public async Task ExecuteAsync(object parameter)
    {
        if (CanExecute(parameter))
        {
            try
            {
                _isExecuting = true;
                InvalidateRequerySuggested();
                await _executeAsync(parameter);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                _isExecuting = false;
                InvalidateRequerySuggested();
            }
        }
    }

    public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}

用法

private IAsyncCommand _myAsyncCommand;

public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(async parameter =>
{
    await Task.Delay(2000);
}));

如果您的Caller有一个名为CanExecute的方法,如下所示:

 private bool CanExecute()
 {
     return SomeCondition && OtherCondition;
 }

然后你就可以将它作为委托类型Func<bool>的实例传递给你的AsyncRelayCommand ,当然,如果你的AsyncRelayCommand定义了带有所需参数的构造函数:

    public AsyncRelayCommand(Func<object, Task> callback, Func<bool> canExecute, Action<Exception> onException = null)
    {
        this.callback = callback;
        this.onException = onException;
        this.canExecute = canExecute;
    }

然后将它传递给构造函数,如下所示:

MyAsyncCommand = new AsyncRelayCommand(ExecuteAsync, CanExecute, ErrorHandler);

因此,您的AsyncRelayCommand将能够调用canExecute委托并获得实际结果。

或者您可以将CanExecute作为属性保留,但是当您创建AsyncRelayCommand ,将其包装到这样的 lambda 表达式中

MyAsyncCommand = new AsyncRelayCommand(ExecuteAsync, () => CanExecute, ErrorHandler);

要将回退逻辑应用于CanExecuteAsyncRelayCommand您可以通过以下方式更改代码:

  • 有一个Func<bool>类型的实例变量被调用,比方说, _canExecute 然后在构造函数中为它分配任何接受作为参数Func<bool> canExecutenull即使它是null 然后在您的public CanExecute(object param)只需检查_canExecute是否为null ,只需返回!IsExecuting就可以了,如果它不是null ,则返回_canExecute返回的任何_canExecute

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM