Skip to content

Commit 40a268f

Browse files
committed
chore: add support for providing a default CancellationToken factory
1 parent a601158 commit 40a268f

File tree

3 files changed

+56
-16
lines changed

3 files changed

+56
-16
lines changed

src/Prism.Core/Commands/AsyncDelegateCommand.cs

+19-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand
1717
private bool _isExecuting = false;
1818
private readonly Func<CancellationToken, Task> _executeMethod;
1919
private Func<bool> _canExecuteMethod;
20+
private Func<CancellationToken> _getCancellationToken = () => CancellationToken.None;
2021

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

137138
/// <summary>
@@ -147,19 +148,30 @@ protected override bool CanExecute(object parameter)
147148
/// <summary>
148149
/// Enables Parallel Execution of Async Tasks
149150
/// </summary>
150-
/// <returns></returns>
151+
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
151152
public AsyncDelegateCommand EnableParallelExecution()
152153
{
153154
_enableParallelExecution = true;
154155
return this;
155156
}
156157

158+
/// <summary>
159+
/// Provides a delegate callback to provide a default CancellationToken when the Command is invoked.
160+
/// </summary>
161+
/// <param name="factory">The default <see cref="CancellationToken"/> Factory.</param>
162+
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
163+
public AsyncDelegateCommand CancellationTokenSourceFactory(Func<CancellationToken> factory)
164+
{
165+
_getCancellationToken = factory;
166+
return this;
167+
}
168+
157169
/// <summary>
158170
/// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
159171
/// </summary>
160172
/// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
161173
/// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
162-
/// <returns>The current instance of DelegateCommand</returns>
174+
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
163175
public AsyncDelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
164176
{
165177
ObservesPropertyInternal(propertyExpression);
@@ -170,7 +182,7 @@ public AsyncDelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpr
170182
/// 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.
171183
/// </summary>
172184
/// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
173-
/// <returns>The current instance of DelegateCommand</returns>
185+
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
174186
public AsyncDelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
175187
{
176188
_canExecuteMethod = canExecuteExpression.Compile();
@@ -182,7 +194,7 @@ public AsyncDelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecute
182194
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
183195
/// </summary>
184196
/// <param name="catch">TThe callback when a specific exception is encountered</param>
185-
/// <returns>The current instance of DelegateCommand</returns>
197+
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
186198
public AsyncDelegateCommand Catch<TException>(Action<TException> @catch)
187199
where TException : Exception
188200
{
@@ -194,7 +206,7 @@ public AsyncDelegateCommand Catch<TException>(Action<TException> @catch)
194206
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
195207
/// </summary>
196208
/// <param name="catch">The generic / default callback when an exception is encountered</param>
197-
/// <returns>The current instance of DelegateCommand</returns>
209+
/// <returns>The current instance of <see cref="AsyncDelegateCommand"/>.</returns>
198210
public AsyncDelegateCommand Catch(Action<Exception> @catch)
199211
{
200212
ExceptionHandler.Register<Exception>(@catch);
@@ -203,7 +215,7 @@ public AsyncDelegateCommand Catch(Action<Exception> @catch)
203215

204216
Task IAsyncCommand.ExecuteAsync(object? parameter)
205217
{
206-
return Execute(default);
218+
return Execute(_getCancellationToken());
207219
}
208220

209221
Task IAsyncCommand.ExecuteAsync(object? parameter, CancellationToken cancellationToken)

src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs

+19-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class AsyncDelegateCommand<T> : DelegateCommandBase, IAsyncCommand
1818
private bool _isExecuting = false;
1919
private readonly Func<T, CancellationToken, Task> _executeMethod;
2020
private Func<T, bool> _canExecuteMethod;
21+
private Func<CancellationToken> _getCancellationToken = () => CancellationToken.None;
2122

2223
/// <summary>
2324
/// Creates a new instance of <see cref="AsyncDelegateCommand{T}"/> with the <see cref="Func{Task}"/> to invoke on execution.
@@ -135,7 +136,7 @@ protected override async void Execute(object parameter)
135136
{
136137
try
137138
{
138-
await Execute((T)parameter);
139+
await Execute((T)parameter, _getCancellationToken());
139140
}
140141
catch (Exception ex)
141142
{
@@ -171,19 +172,30 @@ protected override bool CanExecute(object parameter)
171172
/// <summary>
172173
/// Enables Parallel Execution of Async Tasks
173174
/// </summary>
174-
/// <returns></returns>
175+
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
175176
public AsyncDelegateCommand<T> EnableParallelExecution()
176177
{
177178
_enableParallelExecution = true;
178179
return this;
179180
}
180181

182+
/// <summary>
183+
/// Provides a delegate callback to provide a default CancellationToken when the Command is invoked.
184+
/// </summary>
185+
/// <param name="factory">The default <see cref="CancellationToken"/> Factory.</param>
186+
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
187+
public AsyncDelegateCommand<T> CancellationTokenSourceFactory(Func<CancellationToken> factory)
188+
{
189+
_getCancellationToken = factory;
190+
return this;
191+
}
192+
181193
/// <summary>
182194
/// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
183195
/// </summary>
184196
/// <typeparam name="TType">The type of the return value of the method that this delegate encapsulates</typeparam>
185197
/// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
186-
/// <returns>The current instance of DelegateCommand</returns>
198+
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
187199
public AsyncDelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> propertyExpression)
188200
{
189201
ObservesPropertyInternal(propertyExpression);
@@ -194,7 +206,7 @@ public AsyncDelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> p
194206
/// 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.
195207
/// </summary>
196208
/// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
197-
/// <returns>The current instance of DelegateCommand</returns>
209+
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
198210
public AsyncDelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
199211
{
200212
Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(canExecuteExpression.Body, Expression.Parameter(typeof(T), "o"));
@@ -207,7 +219,7 @@ public AsyncDelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExec
207219
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
208220
/// </summary>
209221
/// <param name="catch">TThe callback when a specific exception is encountered</param>
210-
/// <returns>The current instance of DelegateCommand</returns>
222+
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
211223
public AsyncDelegateCommand<T> Catch<TException>(Action<TException> @catch)
212224
where TException : Exception
213225
{
@@ -219,7 +231,7 @@ public AsyncDelegateCommand<T> Catch<TException>(Action<TException> @catch)
219231
/// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand
220232
/// </summary>
221233
/// <param name="catch">The generic / default callback when an exception is encountered</param>
222-
/// <returns>The current instance of DelegateCommand</returns>
234+
/// <returns>The current instance of <see cref="AsyncDelegateCommand{T}"/>.</returns>
223235
public AsyncDelegateCommand<T> Catch(Action<Exception> @catch)
224236
{
225237
ExceptionHandler.Register<Exception>(@catch);
@@ -231,7 +243,7 @@ async Task IAsyncCommand.ExecuteAsync(object? parameter)
231243
try
232244
{
233245
// If T is not nullable this may throw an exception
234-
await Execute((T)parameter, default);
246+
await Execute((T)parameter, _getCancellationToken());
235247
}
236248
catch (Exception ex)
237249
{

tests/Prism.Core.Tests/Commands/AsyncDelegateCommandFixture.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System.Threading.Tasks;
2-
using System.Threading;
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using System.Windows.Input;
34
using Prism.Commands;
45
using Xunit;
56

@@ -116,4 +117,19 @@ async Task Execute(CancellationToken token)
116117
Assert.True(executionStarted);
117118
Assert.False(executed);
118119
}
120+
121+
[Fact]
122+
public async Task ICommandExecute_UsesDefaultTokenSourceFactory()
123+
{
124+
var cts = new CancellationTokenSource();
125+
var command = new AsyncDelegateCommand((token) => Task.Delay(1000, token))
126+
.CancellationTokenSourceFactory(() => cts.Token);
127+
ICommand iCommand = command;
128+
iCommand.Execute(null);
129+
130+
Assert.True(command.IsExecuting);
131+
cts.Cancel();
132+
133+
Assert.False(command.IsExecuting);
134+
}
119135
}

0 commit comments

Comments
 (0)