Async ICommand implementation

user3292642 picture user3292642 · Mar 10, 2017 · Viewed 6.9k times · Source

I'm facing a strange behavior by my asny ICommand implementation when I tried to disable the command while it's executing by default (even when no CanExecute predicate was passed to it).

public bool CanExecute(object parameter)
{
  if (CanExecutePredicate == null)
  {
    return !mIsExecuting;
  }

  return !mIsExecuting && CanExecutePredicate(parameter);
}

public async void Execute(object parameter)
{
  mIsExecuting = true;
  await ExecuteAsync(parameter);
  mIsExecuting = false;
}

I tried to introduce a private bool, which I set to true just before executing and to false afterwards. When execution is finished the bool is set, but CanExecute is only called after I click a mousebutton or move the mouse or w/e.

Now I tried to call

CanExecute(null);

after

mIsExecuting = false;

but that doesn't help neither. I dont know what I'm missing.

Thanks for your help

EDIT:

To clarify I add the constructors for this class aswell:

 public AsyncRelayCommand(Func<object, Task> execute)
  : this(execute, null)
{
}

public AsyncRelayCommand(Func<object, Task> asyncExecute,
               Predicate<object> canExecutePredicate)
{
  AsyncExecute = asyncExecute;
  CanExecutePredicate = canExecutePredicate;
}

protected virtual async Task ExecuteAsync(object parameter)
{
  await AsyncExecute(parameter);
}

Answer

Seb picture Seb · Mar 10, 2017

In async scenarios, WPF tends not to know when to check CanExecute, that's why you have the "CanExecuteChanged" event in the Icommand interface.

You should have something like this in your command implementation:

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }

    remove { CommandManager.RequerySuggested -= value; }
}

public void RaiseCanExecuteChanged()
{
    CommandManager.InvalidateRequerySuggested();
}

With the code above you can now do this:

public async void Execute(object parameter)
{
    mIsExecuting = true;

    RaiseCanExecuteChanged ( ); // Not necessary if Execute is not called locally

    await ExecuteAsync(parameter);
    mIsExecuting = false;

    RaiseCanExecuteChanged ( );
}

This will tell WPF you want to refresh the CanExecute state of command.