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.
<ButtonContent="..."Command="{Binding AsyncCommand}"/>
<ButtonContent="..."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.
<StackPanelOrientation="Vertical">
<ProgressBarMinimum="0"Maximum="100"Value="{Binding Progress}"Height="20"/>
<ButtonContent="Calculate"Command="{Binding AsyncCommand}"/>
<ButtonContent="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]
publicclass ViewModel {
publicvirtualint 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));
}
}
}
OTHER RELATED ARTICLES:
- Getting Started with DevExpress MVVM Framework. Commands and View Models.
- DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
- DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
- DevExpress MVVM Framework. Introduction to POCO ViewModels.
- DevExpress MVVM Framework. Interaction of ViewModels. Messenger.
- DevExpress MVVM Framework. Using Scaffolding Wizards for building Views.
- DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.
- DevExpress MVVM Framework. Using DataAnnotation attributes and DevExpress Fluent API.
- DevExpress MVVM Framework. Behaviors.
- DevExpress MVVM Framework. TaskbarButtonService, ApplicationJumpListService and NotificationService.
- THIS POST: DevExpress MVVM Framework. Asynchronous Commands.