实现 ICommand 以便一次只能 运行 一种方法,其他方法静默失败?
Implementing ICommand such that only one method may run at a time, others silently fail?
我一直在尝试以这样的方式实现 ICommand,即当它正在执行的方法是 运行ning 时,使用相同类型的命令执行的所有其他方法都会失败(即有一个静态导致执行到 return).
的 IsBusy 变量
需要注意的是,传递给 Command 构造函数的方法可以是同步的,也可以是异步的。
我认为我遇到的问题是调用方法在被调用方完成之前继续,因此将 IsBusy 设置回 false 并允许其他方法 运行。
我在做这件事时遇到了麻烦,而且我知道我可能在做一些非常愚蠢的事情。
尝试的代码:
using System;
using System.Windows.Input;
using System.Threading.Tasks;
namespace DobJenkins
{
public class ExclusiveCommand : ICommand
{
private Command backingCommand;
private static bool IsBusy = false;
private Action action;
public ExclusiveCommand(Action a) {
action = a;
Action guardedAction = (Action)WrapActionWithGuard;
backingCommand = new Command (guardedAction);
backingCommand.CanExecuteChanged += BackingCommand_CanExecuteChanged;
}
void BackingCommand_CanExecuteChanged (object sender, EventArgs e)
{
var ev = CanExecuteChanged;
if (ev != null) {
ev (this, e);
}
}
public void ChangeCanExecute() {
backingCommand.ChangeCanExecute();
}
#region ICommand implementation
public event EventHandler CanExecuteChanged;
public bool CanExecute (object parameter)
{
return backingCommand.CanExecute (parameter);
}
public async void Execute (object parameter)
{
// if (IsBusy) {
// return;
// }
//
// IsBusy = true;
//
// await AsyncWrapper(parameter).ContinueWith(_ => IsBusy=false);
//await AsyncWrapper(parameter);
//IsBusy = false;
backingCommand.Execute(parameter);
}
public async Task AsyncWrapper(object parameter)
{
backingCommand.Execute (parameter);
}
private void WrapActionWithGuard() {
if (IsBusy) {
return;
}
IsBusy = true;
action.Invoke ();
IsBusy = false;
}
#endregion
}
}
命令界面:http://i.stack.imgur.com/YfXoM.png
尝试的解决方案:
如您所见,我用几种不同的方式进行了绑定。我尝试先简单地使用逻辑,然后使用我的支持命令来执行该方法。然后我认为将方法包装在异步方法中将允许我执行 .continuewith 或至少等待它,但这也不起作用。然后我尝试将其包装在逻辑中并将其提供给我的支持命令。
问题:
如果我将异步方法传递到我的命令中,异步方法会正常执行,直到它遇到 'await',此时它将控制权交给命令并将 IsBusy 设置为 false。
我想要的行为是 IsBusy 保持为真,直到完成所有异步或同步方法。我不想让出控制权以将 IsBusy 设置为 false。
我建议你引入一个IAsyncCommand
接口,并要求所有异步命令都继承自该接口:
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
这使您能够异步使用命令。请注意,ICommand.Execute
的每个异步实现都应该是:
async void ICommand.Execute(object parameter)
{
await ExecuteAsync(parameter);
}
那么实现包装命令就非常简单了:
public async Task ExecuteAsync(object parameter)
{
if (IsBusy)
return;
IsBusy = true;
var asyncCommand = backingCommand as IAsyncCommand;
if (asyncCommand != null)
await asyncCommand.ExecuteAsync(parameter);
else
backingCommand.Execute(parameter);
IsBusy = false;
}
请注意,应避免使用 async void
,因为(正如您所发现的),消费/判断何时完成/检测异常/单元测试非常困难。
我有一个MSDN article on async commands that you may find helpful. And on a side note, the current implementation of CanExecuteChanged
in your code is incorrect;如果您要委派 CanExecute
,您也应该委派 CanExecuteChanged
(尽管我认为更合适的实施方式是使用 IsBusy
)。
我一直在尝试以这样的方式实现 ICommand,即当它正在执行的方法是 运行ning 时,使用相同类型的命令执行的所有其他方法都会失败(即有一个静态导致执行到 return).
的 IsBusy 变量需要注意的是,传递给 Command 构造函数的方法可以是同步的,也可以是异步的。
我认为我遇到的问题是调用方法在被调用方完成之前继续,因此将 IsBusy 设置回 false 并允许其他方法 运行。
我在做这件事时遇到了麻烦,而且我知道我可能在做一些非常愚蠢的事情。
尝试的代码:
using System;
using System.Windows.Input;
using System.Threading.Tasks;
namespace DobJenkins
{
public class ExclusiveCommand : ICommand
{
private Command backingCommand;
private static bool IsBusy = false;
private Action action;
public ExclusiveCommand(Action a) {
action = a;
Action guardedAction = (Action)WrapActionWithGuard;
backingCommand = new Command (guardedAction);
backingCommand.CanExecuteChanged += BackingCommand_CanExecuteChanged;
}
void BackingCommand_CanExecuteChanged (object sender, EventArgs e)
{
var ev = CanExecuteChanged;
if (ev != null) {
ev (this, e);
}
}
public void ChangeCanExecute() {
backingCommand.ChangeCanExecute();
}
#region ICommand implementation
public event EventHandler CanExecuteChanged;
public bool CanExecute (object parameter)
{
return backingCommand.CanExecute (parameter);
}
public async void Execute (object parameter)
{
// if (IsBusy) {
// return;
// }
//
// IsBusy = true;
//
// await AsyncWrapper(parameter).ContinueWith(_ => IsBusy=false);
//await AsyncWrapper(parameter);
//IsBusy = false;
backingCommand.Execute(parameter);
}
public async Task AsyncWrapper(object parameter)
{
backingCommand.Execute (parameter);
}
private void WrapActionWithGuard() {
if (IsBusy) {
return;
}
IsBusy = true;
action.Invoke ();
IsBusy = false;
}
#endregion
}
}
命令界面:http://i.stack.imgur.com/YfXoM.png
尝试的解决方案: 如您所见,我用几种不同的方式进行了绑定。我尝试先简单地使用逻辑,然后使用我的支持命令来执行该方法。然后我认为将方法包装在异步方法中将允许我执行 .continuewith 或至少等待它,但这也不起作用。然后我尝试将其包装在逻辑中并将其提供给我的支持命令。
问题:
如果我将异步方法传递到我的命令中,异步方法会正常执行,直到它遇到 'await',此时它将控制权交给命令并将 IsBusy 设置为 false。
我想要的行为是 IsBusy 保持为真,直到完成所有异步或同步方法。我不想让出控制权以将 IsBusy 设置为 false。
我建议你引入一个IAsyncCommand
接口,并要求所有异步命令都继承自该接口:
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
这使您能够异步使用命令。请注意,ICommand.Execute
的每个异步实现都应该是:
async void ICommand.Execute(object parameter)
{
await ExecuteAsync(parameter);
}
那么实现包装命令就非常简单了:
public async Task ExecuteAsync(object parameter)
{
if (IsBusy)
return;
IsBusy = true;
var asyncCommand = backingCommand as IAsyncCommand;
if (asyncCommand != null)
await asyncCommand.ExecuteAsync(parameter);
else
backingCommand.Execute(parameter);
IsBusy = false;
}
请注意,应避免使用 async void
,因为(正如您所发现的),消费/判断何时完成/检测异常/单元测试非常困难。
我有一个MSDN article on async commands that you may find helpful. And on a side note, the current implementation of CanExecuteChanged
in your code is incorrect;如果您要委派 CanExecute
,您也应该委派 CanExecuteChanged
(尽管我认为更合适的实施方式是使用 IsBusy
)。