-
Notifications
You must be signed in to change notification settings - Fork 351
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
namespace CommunityToolkit.Mvvm.Input;
[Flags]
public enum AsyncRelayCommandOptions
{
/// ... existing members ...
/// <summary>
/// <para>Concurrent executions are disallowed. This options makes it so that the same command cannot be invoked while it is already executing.</para>
/// <para>
/// Note that additional considerations should be taken into account in this case:
/// <list type="bullet">
/// <item>If the command supports cancellation, the current invocation can still be canceled before starting a new one.</item>
/// <item>The <see cref="AsyncRelayCommand.ExecutionTask"/> property will always represent the current operation.</item>
/// <item>This option and <see cref="AllowConcurrentExecutions"/> are mutually exclusive.</item>
/// </list>
/// </para>
/// </summary>
ContinueExistingExecutions = 1 << 2
}
public sealed class RelayCommandAttribute : Attribute
{
/// ... existing members ...
/// <summary>
/// Gets or sets a value indicating whether or not to ignore concurrent execution attempts for an asynchronous command.
/// <para>
/// When set for an attribute used on a method that would result in a <see cref="AsyncRelayCommand"/> or an
/// <see cref="AsyncRelayCommand{T}"/> 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 <see cref="AsyncRelayCommand(Func{System.Threading.Tasks.Task}, AsyncRelayCommandOptions)"/>
/// and using the <see cref="AsyncRelayCommandOptions.ContinueExistingExecutions"/> value.
/// </para>
/// </summary>
/// <remarks>
/// <para>Using this property is not valid if the target command doesn't map to an asynchronous command.</para>
/// <para>Using this property in conjunction with <see cref="AllowConcurrentExecutions"/> is not supported.</para>
/// </summary>
public bool ContinueExistingExecutions { get; init; }
}
Usage example
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<T>
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:
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