Skip to content

Commit

Permalink
chore: add support for providing a default CancellationToken factory
Browse files Browse the repository at this point in the history
  • Loading branch information
dansiegel committed Aug 3, 2023
1 parent a601158 commit 40a268f
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 16 deletions.
26 changes: 19 additions & 7 deletions src/Prism.Core/Commands/AsyncDelegateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand
private bool _isExecuting = false;
private readonly Func<CancellationToken, Task> _executeMethod;
private Func<bool> _canExecuteMethod;
private Func<CancellationToken> _getCancellationToken = () => CancellationToken.None;

/// <summary>
/// Creates a new instance of <see cref="AsyncDelegateCommand"/> with the <see cref="Func{Task}"/> to invoke on execution.
Expand Down Expand Up @@ -131,7 +132,7 @@ public bool CanExecute()
/// <param name="parameter">Command Parameter</param>
protected override async void Execute(object parameter)
{
await Execute();
await Execute(_getCancellationToken());
}

/// <summary>
Expand All @@ -147,19 +148,30 @@ protected override bool CanExecute(object parameter)
/// <summary>
/// Enables Parallel Execution of Async Tasks
/// </summary>
/// <returns></returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
public AsyncDelegateCommand EnableParallelExecution()
{
_enableParallelExecution = true;
return this;
}

/// <summary>
/// Provides a delegate callback to provide a default CancellationToken when the Command is invoked.
/// </summary>
/// <param name="factory">The default <see cref="CancellationToken"/> Factory.</param>
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
public AsyncDelegateCommand CancellationTokenSourceFactory(Func<CancellationToken> factory)
{
_getCancellationToken = factory;
return this;
}

/// <summary>
/// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
/// </summary>
/// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
/// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
public AsyncDelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
{
ObservesPropertyInternal(propertyExpression);
Expand All @@ -170,7 +182,7 @@ public AsyncDelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpr
/// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
/// </summary>
/// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
public AsyncDelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
{
_canExecuteMethod = canExecuteExpression.Compile();
Expand All @@ -182,7 +194,7 @@ public AsyncDelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecute
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
/// </summary>
/// <param name="catch">TThe callback when a specific exception is encountered</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
public AsyncDelegateCommand Catch<TException>(Action<TException> @catch)
where TException : Exception
{
Expand All @@ -194,7 +206,7 @@ public AsyncDelegateCommand Catch<TException>(Action<TException> @catch)
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
/// </summary>
/// <param name="catch">The generic / default callback when an exception is encountered</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
public AsyncDelegateCommand Catch(Action<Exception> @catch)
{
ExceptionHandler.Register<Exception>(@catch);
Expand All @@ -203,7 +215,7 @@ public AsyncDelegateCommand Catch(Action<Exception> @catch)

Task IAsyncCommand.ExecuteAsync(object? parameter)
{
return Execute(default);
return Execute(_getCancellationToken());
}

Task IAsyncCommand.ExecuteAsync(object? parameter, CancellationToken cancellationToken)
Expand Down
26 changes: 19 additions & 7 deletions src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class AsyncDelegateCommand<T> : DelegateCommandBase, IAsyncCommand
private bool _isExecuting = false;
private readonly Func<T, CancellationToken, Task> _executeMethod;
private Func<T, bool> _canExecuteMethod;
private Func<CancellationToken> _getCancellationToken = () => CancellationToken.None;

/// <summary>
/// Creates a new instance of <see cref="AsyncDelegateCommand{T}"/> with the <see cref="Func{Task}"/> to invoke on execution.
Expand Down Expand Up @@ -135,7 +136,7 @@ protected override async void Execute(object parameter)
{
try
{
await Execute((T)parameter);
await Execute((T)parameter, _getCancellationToken());
}
catch (Exception ex)
{
Expand Down Expand Up @@ -171,19 +172,30 @@ protected override bool CanExecute(object parameter)
/// <summary>
/// Enables Parallel Execution of Async Tasks
/// </summary>
/// <returns></returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
public AsyncDelegateCommand<T> EnableParallelExecution()
{
_enableParallelExecution = true;
return this;
}

/// <summary>
/// Provides a delegate callback to provide a default CancellationToken when the Command is invoked.
/// </summary>
/// <param name="factory">The default <see cref="CancellationToken"/> Factory.</param>
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
public AsyncDelegateCommand<T> CancellationTokenSourceFactory(Func<CancellationToken> factory)
{
_getCancellationToken = factory;
return this;
}

/// <summary>
/// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
/// </summary>
/// <typeparam name="TType">The type of the return value of the method that this delegate encapsulates</typeparam>
/// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
public AsyncDelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> propertyExpression)
{
ObservesPropertyInternal(propertyExpression);
Expand All @@ -194,7 +206,7 @@ public AsyncDelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> p
/// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
/// </summary>
/// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
public AsyncDelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
{
Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(canExecuteExpression.Body, Expression.Parameter(typeof(T), "o"));
Expand All @@ -207,7 +219,7 @@ public AsyncDelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExec
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
/// </summary>
/// <param name="catch">TThe callback when a specific exception is encountered</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
public AsyncDelegateCommand<T> Catch<TException>(Action<TException> @catch)
where TException : Exception
{
Expand All @@ -219,7 +231,7 @@ public AsyncDelegateCommand<T> Catch<TException>(Action<TException> @catch)
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
/// </summary>
/// <param name="catch">The generic / default callback when an exception is encountered</param>
/// <returns>The current instance of DelegateCommand</returns>
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
public AsyncDelegateCommand<T> Catch(Action<Exception> @catch)
{
ExceptionHandler.Register<Exception>(@catch);
Expand All @@ -231,7 +243,7 @@ async Task IAsyncCommand.ExecuteAsync(object? parameter)
try
{
// If T is not nullable this may throw an exception
await Execute((T)parameter, default);
await Execute((T)parameter, _getCancellationToken());

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-uno / Build Prism.Uno

Converting null literal or possible null value to non-nullable type.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-uno / Build Prism.Uno

Possible null reference argument for parameter 'parameter' in 'Task AsyncDelegateCommand<T>.Execute(T parameter, CancellationToken cancellationToken = default(CancellationToken))'.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Converting null literal or possible null value to non-nullable type.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Possible null reference argument for parameter 'parameter' in 'Task AsyncDelegateCommand<T>.Execute(T parameter, CancellationToken cancellationToken = default(CancellationToken))'.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-core / Build Prism.Core

Converting null literal or possible null value to non-nullable type.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-core / Build Prism.Core

Possible null reference argument for parameter 'parameter' in 'Task AsyncDelegateCommand<T>.Execute(T parameter, CancellationToken cancellationToken = default(CancellationToken))'.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-wpf / Build Prism.Wpf

Converting null literal or possible null value to non-nullable type.

Check warning on line 246 in src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

View workflow job for this annotation

GitHub Actions / build-prism-wpf / Build Prism.Wpf

Possible null reference argument for parameter 'parameter' in 'Task AsyncDelegateCommand<T>.Execute(T parameter, CancellationToken cancellationToken = default(CancellationToken))'.
}
catch (Exception ex)
{
Expand Down
20 changes: 18 additions & 2 deletions tests/Prism.Core.Tests/Commands/AsyncDelegateCommandFixture.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Prism.Commands;
using Xunit;

Expand Down Expand Up @@ -116,4 +117,19 @@ async Task Execute(CancellationToken token)
Assert.True(executionStarted);
Assert.False(executed);
}

[Fact]
public async Task ICommandExecute_UsesDefaultTokenSourceFactory()
{
var cts = new CancellationTokenSource();
var command = new AsyncDelegateCommand((token) => Task.Delay(1000, token))
.CancellationTokenSourceFactory(() => cts.Token);
ICommand iCommand = command;
iCommand.Execute(null);

Assert.True(command.IsExecuting);
cts.Cancel();

Assert.False(command.IsExecuting);
}
}

0 comments on commit 40a268f

Please sign in to comment.