簡體   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