中继命令可以执行和任务

时间:2013-11-24 04:01:31

标签: c# mvvm .net-4.0 task icommand

我想在调用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

5 个答案:

答案 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;
    }
}