CommunityToolkit / CommunityToolkit/dotnet
Non-cancelling, non-concurrent AsyncRelayCommand
- Dominant language
- C#
- Stars
- 3.8k
- Forks
- 400
- PR merge metrics
- No merged PRs in 30d
Description
### Overview
There are contexts where if an async command is executed for any reason while an execution of the command is already in progress, we don't want to cancel the in-progress execution and start over. It would be useful, therefore, to add the ability to set an option for AsyncRelayCommand (and AsyncRelayCommand) that skips the normal behaviour of ExecuteAsync() when an execution is already ongoing.
### API breakdown
```csharp
namespace CommunityToolkit.Mvvm.Input;
[Flags]
public enum AsyncRelayCommandOptions
{
/// ... existing members ...
///
/// Concurrent executions are disallowed. This options makes it so that the same command cannot be invoked while it is already executing.
///
/// Note that additional considerations should be taken into account in this case:
///
/// If the command supports cancellation, the current invocation can still be canceled before starting a new one.
/// The property will always represent the current operation.
/// This option and are mutually exclusive.
///
///
///
ContinueExistingExecutions = 1 << 2
}
public sealed class RelayCommandAttribute : Attribute
{
/// ... existing members ...
///
/// Gets or sets a value indicating whether or not to ignore concurrent execution attempts for an asynchronous command.
///
/// When set for an attribute used on a method that would result in a or an
/// property to be generated, this will modify the behavior of these commands
/// when an execution is invoked while a previous one is still running. It is the same as creating an instance of
/// these command types with a constructor such as
/// and using the value.
///
///
///
/// Using this property is not valid if the target command doesn't map to an asynchronous command.
/// Using this property in conjunction with is not supported.
///
public bool ContinueExistingExecutions { get; init; }
}
```
### Usage example
```csharp
class ViewModel
{
[RelayCommand(ContinueExistingExecutions = true)]
private Task PerformNonRestartableOperationAsync(CancellationToken cancellationToken = default)
{
// do something that shouldn't be restarted whenever the command is invoked
}
}
```
### Breaking change?
I'm not sure
### Alternatives
As an alternative I've created modified `AsyncRelayCommandAlt` and `AsyncRelayCommandAlt` classes in our project based on the MVVM Toolkit implementations, but without the checks for the `AllowConcurrentExecutions` option and with the following changes at the start of the ExecuteAsync methods:
```csharp
public Task ExecuteAsync(object? parameter)
{
// If we're already running, no-op out
if (ExecutionTask is { IsCompleted: false })
return Task.Completed;
// ... existing implementation ...
}
```
This is far less convenient however as it means we can't take advantage of code generation through RelayCommandAttribute.
### Additional context
While I've used the name `ContinueExistingExecutions` I think it's a pretty bad name, I just couldn't think of anything better right now. I'd be very happy if someone could suggest a better name whether or not this proposal gets accepted.
### Help us help you
Yes, but only if others can assist
Contributor guide
Assessment
This issue has not been assessed yet.