我想在调用relay命令时启动一个任务,但是只要该任务正在运行我就想禁用该按钮
以此为例
private ICommand update;
public ICommand Update
{
get
{
if (update == null)
{
update = new RelayCommand(
param => Task.Factory.StartNew(()=> StartUpdate()),
param => true); //true means the button will always be enabled
}
return update;
}
}
检查该任务是否正在运行的最佳方法是什么?
这是我的解决方案,但不确定它是否是最佳方式
class Vm : ObservableObject
{
Task T;
public Vm()
{
T = new Task(() => doStuff());
}
private ICommand myCommand;
public ICommand MyCommand
{
get { return myCommand ?? (myCommand = new RelayCommand( p => { T = new Task(() => doStuff()); T.Start(); }, p => T.Status != TaskStatus.Running)); }
}
private void doStuff()
{
System.Threading.Thread.Sleep(5000);
}
}
更新:这里的每个答案都运行正常,但他们仍然不同意,我只是达到了100的声誉,每当我达到100,我就开始赏金,所以我要找的是一个实现在.net 4.0
中的任务中执行的最佳非内存泄漏异步RelayCommand答案 0 :(得分:22)
我强烈建议您避免使用new Task
以及Task.Factory.StartNew
。在后台线程上启动异步任务的正确方法是Task.Run
。
您可以使用此模式轻松创建异步RelayCommand
:
private bool updateInProgress;
private ICommand update;
public ICommand Update
{
get
{
if (update == null)
{
update = new RelayCommand(
async () =>
{
updateInProgress = true;
Update.RaiseCanExecuteChanged();
await Task.Run(() => StartUpdate());
updateInProgress = false;
Update.RaiseCanExecuteChanged();
},
() => !updateInProgress);
}
return update;
}
}
答案 1 :(得分:8)
我认为,您可以使用AsyncCommand的这种实现。
public class AsyncCommand : ICommand, IDisposable
{
private readonly BackgroundWorker _backgroundWorker = new BackgroundWorker {WorkerSupportsCancellation = true};
private readonly Func<bool> _canExecute;
public AsyncCommand(Action action, Func<bool> canExecute = null, Action<object> completed = null,
Action<Exception> error = null)
{
_backgroundWorker.DoWork += (s, e) =>
{
CommandManager.InvalidateRequerySuggested();
action();
};
_backgroundWorker.RunWorkerCompleted += (s, e) =>
{
if (completed != null && e.Error == null)
completed(e.Result);
if (error != null && e.Error != null)
error(e.Error);
CommandManager.InvalidateRequerySuggested();
};
_canExecute = canExecute;
}
public void Cancel()
{
if (_backgroundWorker.IsBusy)
_backgroundWorker.CancelAsync();
}
public bool CanExecute(object parameter)
{
return _canExecute == null
? !_backgroundWorker.IsBusy
: !_backgroundWorker.IsBusy && _canExecute();
}
public void Execute(object parameter)
{
_backgroundWorker.RunWorkerAsync();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_backgroundWorker != null)
_backgroundWorker.Dispose();
}
}
}
答案 2 :(得分:3)
因此,使用RelayCommand的解决方案几乎可以正常工作。问题是在任务完成运行后UI不会立即更新。这是因为某些东西需要触发ICommand的CanExecuteChanged事件才能使UI正确更新。
解决此问题的一种方法是创建一种新的ICommand。例如:
class AsyncRelayCommand : ICommand
{
private Func<object, Task> _action;
private Task _task;
public AsyncRelayCommand(Func<object,Task> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return _task == null || _task.IsCompleted;
}
public event EventHandler CanExecuteChanged;
public async void Execute(object parameter)
{
_task = _action(parameter);
OnCanExecuteChanged();
await _task;
OnCanExecuteChanged();
}
private void OnCanExecuteChanged()
{
var handler = this.CanExecuteChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
现在您的视图模型可以执行以下操作
private ICommand myCommand;
public ICommand MyCommand
{
get { return myCommand ?? (myCommand = new AsyncRelayCommand(p => Task.Factory.StartNew(doStuff))); }
}
private void doStuff()
{
System.Threading.Thread.Sleep(5000);
}
或者你可以让你的doStuff函数成为像这样的“异步”函数
private ICommand myCommand2;
public ICommand MyCommand2
{
get { return myCommand2 ?? (myCommand2 = new AsyncRelayCommand(p => doStuff2())); }
}
private async Task doStuff2()
{
await Task.Delay(5000);
}
答案 3 :(得分:0)
您可以拥有一个静态变量IsRunning
,您可以在任务启动时将其设置为True,在完成时设置为false,并将该启用按钮绑定到IsRunning
的状态
答案 4 :(得分:0)
我试图避免Prism库从引用程序集的安装角度尽可能地保持我的控制,我最终得到了这个解决方案
_cmd = new RelayCommand(async delegate
{
await Task.Run(() => <YourMethod>());
}, delegate { return !IsInProgress; }) );
似乎运作良好。 (如果您不需要传递commandParameter)。不幸的是,这仍然是一个问题。
RelayCommand类继承自ICommand
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
CanExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return _canExecute != null && _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = CanExecuteChangedInternal;
if (handler != null)
{
//DispatcherHelper.BeginInvokeOnUIThread(() => handler.Invoke(this, EventArgs.Empty));
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
_canExecute = _ => false;
_execute = _ => { return; };
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}