DevExpress MVVM Framework. Asynchronous Commands.

WPF Team Blog
15 August 2014

OTHER RELATED ARTICLES:

  1. Getting Started with DevExpress MVVM Framework. Commands and View Models.
  2. DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
  3. DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
  4. DevExpress MVVM Framework. Introduction to POCO ViewModels.
  5. DevExpress MVVM Framework. Interaction of ViewModels. Messenger.
  6. DevExpress MVVM Framework. Using Scaffolding Wizards for building Views.
  7. DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.
  8. DevExpress MVVM Framework. Using DataAnnotation attributes and DevExpress Fluent API.
  9. DevExpress MVVM Framework. Behaviors.
  10. DevExpress MVVM Framework. TaskbarButtonService, ApplicationJumpListService and NotificationService.
  11. THIS POST: DevExpress MVVM Framework. Asynchronous Commands.
  12. DevExpress MVVM Framework. Converters.


In one of my previous posts, we reviewed two ICommand implementations: DelegateCommand and DelegateCommand<T>. Today, we’ll examine two more implementations: AsyncCommand and AsyncCommand<T>.

These commands may be useful when running time-consuming operations on a separate thread without freezing the UI. For instance, if you need to execute a calculation, you can bind a button to an AsyncCommand. When an end-user clicks the button, the command starts the calculation task and the button remains disabled. When the task finishes, the button is re-enabled.

Creating AsyncCommands

The AsyncCommand and AsyncCommand<T> are created similarly to the regular DelegateCommands. There are two constructors: one constructor accepts an Execute delegate parameter; the second constructor accepts Execute and CanExecute delegate parameters. The Execute delegate should return a Task object. Both pairs of asynchronous command constructors support the UseCommandManager optional parameter (set to True by default) that determines whether the CommandManager is used to raise the CanExecuteChanged event.

AsyncCommand<string> asyncCommand = new AsyncCommand<string>(Calculate, CanCalculate, true);
Task Calculate(string parameter) {
    return Task.Factory.StartNew(CalculateCore);
}
bool CanCalculate(string parameter) {
    //...
}
void CalculateCore() {
    //...
}

Running AsyncCommands

You can bind AsyncCommands in the same manner as ICommands.

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

Preventing simultaneous execution

The AsyncCommand and AsyncCommand<T> classes provide an IsExecuting property. While the command task is executing, IsExecuting is True and the AsyncCommand.CanExecute method always returns False regardless of the contents of your CanExecute delegate. This feature disables controls bound to the command until the pending command task is finished.

You can explicitly disable this behavior by setting the AsyncCommand.AllowMultipleExecution property to True. In which case, the AsyncCommand.CanExecute method returns a value based on your provided CanExecute delegate.

Canceling command executions

If necessary, you can implement a mechanism to cancel a running command. The asynchronous commands have an IsCancellationRequested property you can check from the task Action.

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

In this case, you can cancel command execution with AsyncCommand.CancelCommand. This command sets the AsyncCommand.IsCancellationRequested property to True, so you can stop calculation in the Execute delegate.

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

You can use the AsyncCommand.CancellationTokenSource property if you need more control over the cancellation process. For instance:

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

AsyncCommands and IDispatcherService

Sometimes, it’s necessary to report progress on the task to the main thread. This can be done by using the IDispatcherService (see Introduction to Services for more details).

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

AsyncCommands in POCO

POCO provides the capability to automatically create AsyncCommands based on public functions which return a Task object (the function also needs to be defined parameterless or with a single parameter). To access the IsExecuting and IsCancellationRequested command properties, you can use the DevExpress.Mvvm.POCO.POCOViewModelExtensions class that provides the GetAsyncCommand method. Below is an example of a POCO ViewModel that provides the CalculateCommand AsyncCommand.

[POCOViewModel]
public class ViewModel {
    public virtual int Progress { get; set; }
    public Task Calculate() {
        return Task.Factory.StartNew(CalculateCore);
    }
    void CalculateCore() {
        for(int i = 0; i <= 100; i++) {
            if(this.GetAsyncCommand(x => x.Calculate()).IsCancellationRequested) return;
            Progress = i;
            Thread.Sleep(TimeSpan.FromSeconds(0.1));
        }
    }
}

 

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.