-
Notifications
You must be signed in to change notification settings - Fork 396
Add FlowExceptionsToTaskScheduler command option #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
6d73083
79204ab
dba9c41
799558c
0d7ec7a
923d479
fdda4c6
612055b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -59,9 +59,9 @@ public sealed class AsyncRelayCommand : IAsyncRelayCommand, ICancellationAwareCo | |||||
| private readonly Func<bool>? canExecute; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Indicates whether or not concurrent executions of the command are allowed. | ||||||
| /// The options being set for the current command. | ||||||
| /// </summary> | ||||||
| private readonly bool allowConcurrentExecutions; | ||||||
| private readonly AsyncRelayCommandOptions options; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>. | ||||||
|
|
@@ -91,14 +91,14 @@ public AsyncRelayCommand(Func<Task> execute) | |||||
| /// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class. | ||||||
| /// </summary> | ||||||
| /// <param name="execute">The execution logic.</param> | ||||||
| /// <param name="allowConcurrentExecutions">Whether or not to allow concurrent executions of the command.</param> | ||||||
| /// <param name="options">The options to use to configure the async command.</param> | ||||||
| /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="execute"/> is <see langword="null"/>.</exception> | ||||||
| public AsyncRelayCommand(Func<Task> execute, bool allowConcurrentExecutions) | ||||||
| public AsyncRelayCommand(Func<Task> execute, AsyncRelayCommandOptions options) | ||||||
| { | ||||||
| ArgumentNullException.ThrowIfNull(execute); | ||||||
|
|
||||||
| this.execute = execute; | ||||||
| this.allowConcurrentExecutions = allowConcurrentExecutions; | ||||||
| this.options = options; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
|
|
@@ -117,14 +117,14 @@ public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute) | |||||
| /// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class. | ||||||
| /// </summary> | ||||||
| /// <param name="cancelableExecute">The cancelable execution logic.</param> | ||||||
| /// <param name="allowConcurrentExecutions">Whether or not to allow concurrent executions of the command.</param> | ||||||
| /// <param name="options">The options to use to configure the async command.</param> | ||||||
| /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="cancelableExecute"/> is <see langword="null"/>.</exception> | ||||||
| public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, bool allowConcurrentExecutions) | ||||||
| public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, AsyncRelayCommandOptions options) | ||||||
| { | ||||||
| ArgumentNullException.ThrowIfNull(cancelableExecute); | ||||||
|
|
||||||
| this.cancelableExecute = cancelableExecute; | ||||||
| this.allowConcurrentExecutions = allowConcurrentExecutions; | ||||||
| this.options = options; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
|
|
@@ -147,16 +147,16 @@ public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute) | |||||
| /// </summary> | ||||||
| /// <param name="execute">The execution logic.</param> | ||||||
| /// <param name="canExecute">The execution status logic.</param> | ||||||
| /// <param name="allowConcurrentExecutions">Whether or not to allow concurrent executions of the command.</param> | ||||||
| /// <param name="options">The options to use to configure the async command.</param> | ||||||
| /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="execute"/> or <paramref name="canExecute"/> are <see langword="null"/>.</exception> | ||||||
| public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute, bool allowConcurrentExecutions) | ||||||
| public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute, AsyncRelayCommandOptions options) | ||||||
| { | ||||||
| ArgumentNullException.ThrowIfNull(execute); | ||||||
| ArgumentNullException.ThrowIfNull(canExecute); | ||||||
|
|
||||||
| this.execute = execute; | ||||||
| this.canExecute = canExecute; | ||||||
| this.allowConcurrentExecutions = allowConcurrentExecutions; | ||||||
| this.options = options; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
|
|
@@ -179,16 +179,16 @@ public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<b | |||||
| /// </summary> | ||||||
| /// <param name="cancelableExecute">The cancelable execution logic.</param> | ||||||
| /// <param name="canExecute">The execution status logic.</param> | ||||||
| /// <param name="allowConcurrentExecutions">Whether or not to allow concurrent executions of the command.</param> | ||||||
| /// <param name="options">The options to use to configure the async command.</param> | ||||||
| /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="cancelableExecute"/> or <paramref name="canExecute"/> are <see langword="null"/>.</exception> | ||||||
| public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute, bool allowConcurrentExecutions) | ||||||
| public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute, AsyncRelayCommandOptions options) | ||||||
| { | ||||||
| ArgumentNullException.ThrowIfNull(cancelableExecute); | ||||||
| ArgumentNullException.ThrowIfNull(canExecute); | ||||||
|
|
||||||
| this.cancelableExecute = cancelableExecute; | ||||||
| this.canExecute = canExecute; | ||||||
| this.allowConcurrentExecutions = allowConcurrentExecutions; | ||||||
| this.options = options; | ||||||
| } | ||||||
|
|
||||||
| private Task? executionTask; | ||||||
|
|
@@ -238,7 +238,7 @@ static async void MonitorTask(AsyncRelayCommand @this, Task task) | |||||
| @this.PropertyChanged?.Invoke(@this, CanBeCanceledChangedEventArgs); | ||||||
| } | ||||||
|
|
||||||
| if (!@this.allowConcurrentExecutions) | ||||||
| if ((@this.options & AsyncRelayCommandOptions.AllowConcurrentExecutions) == 0) | ||||||
| { | ||||||
| @this.CanExecuteChanged?.Invoke(@this, EventArgs.Empty); | ||||||
| } | ||||||
|
|
@@ -273,13 +273,20 @@ public bool CanExecute(object? parameter) | |||||
| { | ||||||
| bool canExecute = this.canExecute?.Invoke() != false; | ||||||
|
|
||||||
| return canExecute && (this.allowConcurrentExecutions || ExecutionTask is not { IsCompleted: false }); | ||||||
| return canExecute && ((this.options & AsyncRelayCommandOptions.AllowConcurrentExecutions) != 0 || ExecutionTask is not { IsCompleted: false }); | ||||||
| } | ||||||
|
|
||||||
| /// <inheritdoc/> | ||||||
| public void Execute(object? parameter) | ||||||
| { | ||||||
| _ = ExecuteAsync(parameter); | ||||||
| Task executionTask = ExecuteAsync(parameter); | ||||||
|
|
||||||
| // If exceptions shouldn't flow to the task scheduler, await the resulting task. This is | ||||||
| // delegated to a separate method to keep this one more compact in case the option is set. | ||||||
| if ((this.options & AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler) == 0) | ||||||
| { | ||||||
| AwaitAndThrowIfFailed(executionTask); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// <inheritdoc/> | ||||||
|
|
@@ -304,7 +311,7 @@ public Task ExecuteAsync(object? parameter) | |||||
| } | ||||||
|
|
||||||
| // If concurrent executions are disabled, notify the can execute change as well | ||||||
| if (!this.allowConcurrentExecutions) | ||||||
| if ((this.options & AsyncRelayCommandOptions.AllowConcurrentExecutions) == 0) | ||||||
| { | ||||||
| CanExecuteChanged?.Invoke(this, EventArgs.Empty); | ||||||
| } | ||||||
|
|
@@ -323,4 +330,13 @@ public void Cancel() | |||||
| PropertyChanged?.Invoke(this, IsCancellationRequestedChangedEventArgs); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Awaits an input <see cref="Task"/> and throws an exception on the calling context, if the task fails. | ||||||
| /// </summary> | ||||||
| /// <param name="executionTask">The input <see cref="Task"/> instance to await.</param> | ||||||
| internal static async void AwaitAndThrowIfFailed(Task executionTask) | ||||||
| { | ||||||
| await executionTask; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add the
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't add this on purpose to be honest. The idea is that if one of the main points of having exceptions be rethrown is to also help debugging things (other than not get the app into a weird invalid state of course), I feel like it'd be clearer for users to see the exceptions be thrown on the calling context, which would generally be the UI thread. This is also handled in special ways on many frameworks, versus exceptions being thrown on random thread pool threads. I know there's a slight overhead in this, but I feel like that's fine here given it's not like this would be used in a hot path like in some asynchronous code dealing with loops of web requests or anything like that 🤔
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine then. I thought that could be intentional, but not sure if with the |
||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.