Asynchronous Commands

  • 4 minutes to read

Asynchronous commands can be useful if you need to run a time-consuming operation in a separate thread without freezing the UI. For instance, if you need to calculate something, you can bind a button to an asynchronous command. When an end user clicks the button, the command starts the calculation process and the button becomes disabled. When the process is done, the button is enabled.

The following asynchronous commands are available.

  • AsyncCommand<T> - Specifies a command whose Execute and CanExecute delegates accept a single parameter of type T.
  • AsyncCommand - Specifies a command whose Execute and CanExecute delegates do not have any parameters.

Create AsyncCommands

The AsyncCommand and AsyncCommand<T> can be created similarly to the regular Delegate Commands. There are two constructors: a constructor that accepts the Execute delegate, and a second constructor that accepts Execute and CanExecute delegates. The Execute delegate should return a Task object.

public MyViewModel() {
    MyAsyncCommand = new AsyncCommand<string>(Calculate, CanCalculate);
}

public AsyncCommand<string> MyAsyncCommand { get; private set; }
Task Calculate(string parameter) {
    //...
}
bool CanCalculate(string parameter) {
    //...
}

Run AsyncCommands

You can bind to AsyncCommands in the same manner as any ICommand.

<Button Content="..." Command="{Binding MyAsyncCommand}"/>
<Button Content="..." Command="{Binding MyAsyncCommand}" CommandParameter="..."/>

Prevent Simultaneous Execution

The AsyncCommand and AsyncCommand<T> classes have the IsExecuting property. While the command execution task is working, this property equals True and the AsyncCommand.CanExecute method always returns False, regardless of what you implemented in the CanExecute delegate. This functionality allows you to disable a control bound to the command until the previous command execution is completed.

Set the AsyncCommand.AllowMultipleExecution property to True in the command’s constructor to disable this behavior. In this case, the AsyncCommand.CanExecute method returns a value based on your CanExecute delegate implementation.

 MyAsyncCommand = new AsyncCommand<string>(Calculate, CanCalculate, allowMultipleExecution: true);

Cancel Command Executions

The AsyncCommands have the IsCancellationRequested property, which you can check in the execution method to implement canceling the command execution.

public AsyncCommand MyAsyncCommand { get; private set; }

public MyViewModel() {
    MyAsyncCommand = new AsyncCommand(Calculate);
}

Task Calculate() {
    return Task.Factory.StartNew(CalculateCore);
}
void CalculateCore() {
    for(int i = 0; i <= 100; i++) {
        if(myAsyncCommand.IsCancellationRequested) return;
        Progress = i;
        Thread.Sleep(TimeSpan.FromSeconds(0.1));
    }
}

The AsyncCommand.IsCancellationRequested property is set to True when AsyncCommand.CancelCommand is invoked. You can bind a control to the CancelCommand as follows.

<StackPanel Orientation="Vertical">
    <ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" Height="20"/>
    <Button Content="Calculate" Command="{Binding MyAsyncCommand}"/>
    <Button Content="Cancel" Command="{Binding MyAsyncCommand.CancelCommand}"/>
</StackPanel>

If you need more control over the cancellation process, use the AsyncCommand.CancellationTokenSource property. For instance:

public AsyncCommand MyAsyncCommand { get; private set; }

public MyViewModel() {
    MyAsyncCommand = new AsyncCommand(Calculate);
}
Task Calculate() {
    return Task.Factory.StartNew(CalculateCore, MyAsyncCommand.CancellationTokenSource.Token).
        ContinueWith(x => MessageBoxService.ShowAsync(x.IsCanceled.ToString()));
}
void CalculateCore() {
    for(int i = 0; i <= 100; i++) {
        MyAsyncCommand.CancellationTokenSource.Token.ThrowIfCancellationRequested();
        Progress = i;
        Thread.Sleep(TimeSpan.FromSeconds(0.1));
    }
}

AsyncCommands and IDispatcherService

If you need to access the execution process from the main thread, use the IDispatcherService as follows.

IDispatcherService DispatcherService { get { ... } }
void CalculateCore() {
    ...
    DispatcherService.BeginInvoke(() => {
        ...
    });
    ...
}