diff --git a/src/Prism.Core/Commands/AsyncDelegateCommand.cs b/src/Prism.Core/Commands/AsyncDelegateCommand.cs
index ad4082d160..8c7eec460f 100644
--- a/src/Prism.Core/Commands/AsyncDelegateCommand.cs
+++ b/src/Prism.Core/Commands/AsyncDelegateCommand.cs
@@ -24,7 +24,11 @@ public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand
///
/// The to invoke when is called.
public AsyncDelegateCommand(Func executeMethod)
+#if NET6_0_OR_GREATER
+ : this (c => executeMethod().WaitAsync(c), () => true)
+#else
: this(c => executeMethod(), () => true)
+#endif
{
}
@@ -46,7 +50,11 @@ public AsyncDelegateCommand(Func executeMethod)
/// The to invoke when is called.
/// The delegate to invoke when is called
public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod)
+#if NET6_0_OR_GREATER
+ : this(c => executeMethod().WaitAsync(c), canExecuteMethod)
+#else
: this(c => executeMethod(), canExecuteMethod)
+#endif
{
}
@@ -78,16 +86,17 @@ public bool IsExecuting
///
/// Executes the command.
///
- public async Task Execute(CancellationToken cancellationToken = default)
+ public async Task Execute(CancellationToken? cancellationToken = null)
{
+ var token = cancellationToken ?? _getCancellationToken();
try
{
+ if (!_enableParallelExecution && IsExecuting)
+ return;
+
IsExecuting = true;
- await _executeMethod(cancellationToken);
- }
- catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
- {
- // Do nothing... the Task was cancelled
+ await _executeMethod(token)
+ .ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -132,7 +141,11 @@ public bool CanExecute()
/// Command Parameter
protected override async void Execute(object? parameter)
{
- await Execute(_getCancellationToken());
+ // We don't want to wrap this in a try/catch because we already handle
+ // or mean to rethrow the exception in the call with the CancellationToken.
+ var cancellationToken = _getCancellationToken();
+ await Execute(cancellationToken)
+ .ConfigureAwait(false);
}
///
@@ -155,6 +168,14 @@ public AsyncDelegateCommand EnableParallelExecution()
return this;
}
+ ///
+ /// Sets the based on the specified timeout.
+ ///
+ /// A specified timeout.
+ /// The current instance of .
+ public AsyncDelegateCommand CancelAfter(TimeSpan timeout) =>
+ CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token);
+
///
/// Provides a delegate callback to provide a default CancellationToken when the Command is invoked.
///
diff --git a/src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs b/src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs
index ed257ce196..a449f5c67b 100644
--- a/src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs
+++ b/src/Prism.Core/Commands/AsyncDelegateCommand{T}.cs
@@ -25,7 +25,11 @@ public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand
///
/// The to invoke when is called.
public AsyncDelegateCommand(Func executeMethod)
+#if NET6_0_OR_GREATER
+ : this((p,t) => executeMethod(p).WaitAsync(t), _ => true)
+#else
: this((p, t) => executeMethod(p), _ => true)
+#endif
{
}
@@ -47,7 +51,11 @@ public AsyncDelegateCommand(Func executeMethod)
/// The to invoke when is called.
/// The delegate to invoke when is called
public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod)
+#if NET6_0_OR_GREATER
+ : this((p, c) => executeMethod(p).WaitAsync(c), canExecuteMethod)
+#else
: this((p, c) => executeMethod(p), canExecuteMethod)
+#endif
{
}
@@ -80,16 +88,18 @@ public bool IsExecuting
///
/// Executes the command.
///
- public async Task Execute(T parameter, CancellationToken cancellationToken = default)
+ public async Task Execute(T parameter, CancellationToken? cancellationToken = null)
{
+ var token = cancellationToken ?? _getCancellationToken();
+
try
{
+ if (!_enableParallelExecution && IsExecuting)
+ return;
+
IsExecuting = true;
- await _executeMethod(parameter, cancellationToken);
- }
- catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
- {
- // Do nothing... the Task was cancelled
+ await _executeMethod(parameter, token)
+ .ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -134,9 +144,11 @@ public bool CanExecute(T parameter)
/// Command Parameter
protected override async void Execute(object? parameter)
{
+ var cancellationToken = _getCancellationToken();
+ T parameterAsT;
try
{
- await Execute((T)parameter!, _getCancellationToken());
+ parameterAsT = (T)parameter!;
}
catch (Exception ex)
{
@@ -144,7 +156,14 @@ protected override async void Execute(object? parameter)
throw;
ExceptionHandler.Handle(ex, parameter);
+ return;
}
+
+ // If we had an exception casting the parameter to T ,
+ // we would have already returned. We want to surface any
+ // exceptions thrown by the Execute method.
+ await Execute(parameterAsT, cancellationToken)
+ .ConfigureAwait(false);
}
///
@@ -179,6 +198,14 @@ public AsyncDelegateCommand EnableParallelExecution()
return this;
}
+ ///
+ /// Sets the based on the specified timeout.
+ ///
+ /// A specified timeout.
+ /// The current instance of .
+ public AsyncDelegateCommand CancelAfter(TimeSpan timeout) =>
+ CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token);
+
///
/// Provides a delegate callback to provide a default CancellationToken when the Command is invoked.
///
diff --git a/tests/Prism.Core.Tests/Commands/AsyncDelegateCommandFixture.cs b/tests/Prism.Core.Tests/Commands/AsyncDelegateCommandFixture.cs
index 99cda221a3..da4a5ad2d7 100644
--- a/tests/Prism.Core.Tests/Commands/AsyncDelegateCommandFixture.cs
+++ b/tests/Prism.Core.Tests/Commands/AsyncDelegateCommandFixture.cs
@@ -129,7 +129,18 @@ public async Task ICommandExecute_UsesDefaultTokenSourceFactory()
Assert.True(command.IsExecuting);
cts.Cancel();
+ await Task.Delay(10);
Assert.False(command.IsExecuting);
}
+
+ [Fact]
+ public void ICommandExecute_HandlesErrorOnce()
+ {
+ var handled = 0;
+ ICommand command = new AsyncDelegateCommand(str => throw new System.Exception("Test"))
+ .Catch(ex => handled++);
+ command.Execute(string.Empty);
+ Assert.Equal(1, handled);
+ }
}