【问题标题】:Relay Command can execute and a Task中继命令可以执行和一个任务
【发布时间】:2013-11-24 04:01:30
【问题描述】:

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

举个例子

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

【问题讨论】:

  • @MerickOWA 它不适用于 Task.IsCompleted,使用 T.Status != TaskStatus.Running 是最好的选择

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


【解决方案1】:

我强烈建议您避免使用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;
  }
}

【讨论】:

  • 当您已经指定异步 lambda 时,为什么还要在 Task.Run 上等待?如果 RelayCommand 接受异步 lambda,Task.Run 的意义何在,它应该异步执行。
  • RelayCommand 不接受async lambda(即Func<Task>); lambda 用于将async 代码包装成async void(即Action)。 Task.Run 仅存在是因为操作的原始代码也在后台线程上运行 StartUpdate;在理想情况下(一直async),Task.Run 是不必要的。
  • 那么需要将其标记为异步吗?难道你不能通过在 Dispatcher 的 BeginInvoke 中包装 Raise Event 来在 Task.Run 中调用 Raise Event 吗?
  • 除了直接使用Dispatcher 是一种代码味道之外,我认为它不会按照您的想法工作。您需要在某个时候使用async void 方法。
【解决方案2】:

我认为,您可以使用 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();
        }
    }
}

【讨论】:

  • BackgroundWorker 是编组到 UI 线程还是它开始的任何线程?是否需要编组回 UI 线程?
【解决方案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);
}

【讨论】:

  • 两个问题:1)StartNew is dangerous (as I explain on my blog),2)你对CanExecuteChangedleaks memory的实现。
  • @StephenCleary 1) 我的 AsyncRelayCommand 实现不使用 Task.StartNew,我只是调整了给定的示例代码以使其尽可能易于理解。 2) 根据我的阅读,这是 4.5 中的 fixed,专注于更改控件或 ICommandSource,而不是更改 ICommand 的实现。
【解决方案4】:

您可以有一个静态变量IsRunning,您可以在任务开始时将其设置为 True,在任务完成时设置为 false,然后将启用的按钮绑定到 IsRunning 的状态

【讨论】:

  • 我已经这样做了,但是没有额外的变量就没有办法了吗?
  • 嗯,有人需要知道一些事情......否则你会怎么做? :)
  • 也许通过使任务全局化?这会令人不安,因为我有很多命令,并且只要相关任务正在运行,我就需要禁用按钮
  • 静态标志只应在最简单的场景中使用,当事情开始变得更复杂时,这种方法很快就会崩溃。
  • @slugster 这对我来说听起来很简单。否则,你在暗示什么?好像我投了反对票,而 OP 从来没有其他选择......
【解决方案5】:

我试图避免使用 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;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-07
    • 2013-02-13
    • 1970-01-01
    • 2012-10-07
    • 1970-01-01
    • 2012-02-26
    相关资源
    最近更新 更多