C# 中继命令可以执行和执行任务

C# 中继命令可以执行和执行任务,c#,mvvm,.net-4.0,task,icommand,C#,Mvvm,.net 4.0,Task,Icommand,我想在调用中继命令时启动一个任务,但是只要该任务正在运行,我就想禁用该按钮 就拿这个例子来说 private ICommand update; public ICommand Update { get { if (update == null) { update = new RelayCommand(

我想在调用中继命令时启动一个任务,但是只要该任务正在运行,我就想禁用该按钮

就拿这个例子来说

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中的任务内执行的最佳无内存泄漏异步Relay命令的实现。您可以有一个静态变量
IsRunning
,您可以在任务开始时将其设置为True,在任务结束时将其设置为false,只需将启用的按钮绑定到
正在运行的状态

,这样您使用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);
    }
  }
或者您可以将doStuff函数设置为“异步”函数,如下所示

private ICommand myCommand2;
public ICommand MyCommand2
{
  get { return myCommand2 ?? (myCommand2 = new AsyncRelayCommand(p => doStuff2())); }
}
private async Task doStuff2()
{
  await Task.Delay(5000);
}

我强烈建议您避免使用
新任务
以及
任务.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;
  }
}

我认为,您可以使用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();
        }
    }
}
公共类异步命令:ICommand,IDisposable
{
private readonly BackgroundWorker _BackgroundWorker=new BackgroundWorker{WorkerSupportsCancellation=true};
私有只读功能可执行;
public AsyncCommand(Action-Action,Func-canExecute=null,Action-completed=null,
操作错误=空)
{
_backgroundWorker.DoWork+=(s,e)=>
{
CommandManager.InvalidateRequestSuggested();
动作();
};
_backgroundWorker.RunWorkerCompleted+=(s,e)=>
{
如果(已完成!=null&&e.错误==null)
完成(即结果);
如果(错误!=null&&e.错误!=null)
错误(即错误);
CommandManager.InvalidateRequestSuggested();
};
_canExecute=canExecute;
}
公开作废取消()
{
如果(_backgroundWorker.IsBusy)
_backgroundWorker.CancelAsync();
}
公共布尔CanExecute(对象参数)
{
返回_canExecute==null
?!\u后台工作人员很忙
:!\u backgroundWorker.IsBusy&&u canExecute();
}
public void Execute(对象参数)
{
_backgroundWorker.RunWorkerAsync();
}
公共事件事件处理程序CanExecuteChanged
{
添加{CommandManager.RequerySuggested+=value;}
删除{CommandManager.RequerySuggested-=value;}
}
公共空间处置()
{
处置(真实);
总干事(本);
}
受保护的虚拟void Dispose(bool disposing)
{
如果(处置)
{
if(_backgroundWorker!=null)
_backgroundWorker.Dispose();
}
}
}

我试图避免使用Prism库,以从参考程序集的装载角度尽可能地简化我的控制,我最终得到了这个解决方案

_cmd = new RelayCommand(async delegate
{
   await Task.Run(() => <YourMethod>());
}, delegate { return !IsInProgress; }) );
\u cmd=new RelayCommand(异步委托
{
等待任务。运行(()=>());
},委托{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;
    }
}
公共类RelayCommand:ICommand
{
私人行动——执行;
私有谓词_canExecute;
私有事件处理程序CanExecuteChangeInternal;
公共中继命令(操作执行)
:此(执行,默认CanExecute)
{
}
公共RelayCommand(操作执行,谓词canExecute)
{
if(execute==null)
{
抛出新的ArgumentNullException(“执行”);
}
如果(canExecute==null)
{
抛出新ArgumentNullException(“canExecute”);
}
_执行=执行;
_canExecute=canExecute;
}
公共事件事件处理程序CanExecuteChanged
{
添加
{
CommandManager.RequerySuggested+=值;
CanExecuteChangeInternal+=值;
}
去除
{
CommandManager.RequerySuggested-=值;
CanExecuteChangeInternal-=值;
}
}
公共布尔CanExecute(对象参数)
{
return _canExecute!=null&&u canExecute(参数);
}
public void Execute(对象参数)
{
_执行(参数);
}
CanExecuteChanged()上的公共无效
{
EventHandler handler=CanExecuteChangeInternal;
if(处理程序!=null)
{
//DispatcherHelper.BeginInvokeoUnithread(()=>handler.Invoke(this,EventArgs.Empty));
Invoke(this,EventArgs.Empty);
}
}
公共空间销毁()
{
_canExecute==>false;
_execute=\=>{return;};
}
私有静态bool DefaultCanExecute(对象参数)
{
返回true;
}
}

我已经这么做了,但是没有额外的变量就没有办法了吗?嗯,有人需要知道一些事情。。。否则你会怎么做?:)也许通过使任务全局化,这会令人不安,因为我有很多命令,只要相关任务正在运行,我就需要禁用按钮。静态标志只应在最简单的场景中使用,这种方法在出现问题时会很快失效
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;
    }
}