[英]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
。CanExecute
为false
,因此会自动禁用控制。执行
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);
要将回退逻辑应用于CanExecute
的AsyncRelayCommand
您可以通过以下方式更改代码:
Func<bool>
类型的实例变量被调用,比方说, _canExecute
。 然后在构造函数中为它分配任何接受作为参数Func<bool> canExecute
的null
即使它是null
。 然后在您的public CanExecute(object param)
只需检查_canExecute
是否为null
,只需返回!IsExecuting
就可以了,如果它不是null
,则返回_canExecute
返回的任何_canExecute
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.