-
Notifications
You must be signed in to change notification settings - Fork 5
V9.0.6/awaiter #123
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
V9.0.6/awaiter #123
Changes from 2 commits
769f989
a84a067
e900c82
03ee55e
949f6f0
3a45da8
1464b91
3741b92
4773b02
ca3f14f
8fd4f30
019a78d
80197c9
b9b3274
19375cf
8ee7a7b
a243970
a7543d7
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace Cuemon.Threading | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Provides options that are related to asynchronous run operations. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <seealso cref="AsyncOptions"/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class AsyncRunOptions : AsyncOptions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Initializes a new instance of the <see cref="AsyncRunOptions"/> class. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// The following table shows the initial property values for an instance of <see cref="AsyncRunOptions"/>. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <list type="table"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <listheader> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <term>Property</term> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <description>Initial Value</description> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </listheader> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <term><see cref="Timeout"/></term> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <description><c>00:00:05</c> (5 seconds)</description> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <term><see cref="Delay"/></term> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <description><c>00:00:00.1000000</c> (100 milliseconds)</description> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </list> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AsyncRunOptions() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timeout = TimeSpan.FromSeconds(5); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Delay = TimeSpan.FromMilliseconds(100); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Gets or sets the timeout for the asynchronous operation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <value>The timeout for the asynchronous operation. The default is 5 seconds.</value> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public TimeSpan Timeout { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Gets or sets the delay between asynchronous operation attempts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <value>The delay between asynchronous operation attempts. The default is 100 milliseconds.</value> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public TimeSpan Delay { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+47
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. 🛠️ Refactor suggestion Add guards in property setters Without checks, a caller can later inject invalid values after construction. -public TimeSpan Timeout { get; set; }
+private TimeSpan _timeout;
+public TimeSpan Timeout
+{
+ get => _timeout;
+ set
+ {
+ if (value < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(value));
+ _timeout = value;
+ }
+}
@@
-public TimeSpan Delay { get; set; }
+private TimeSpan _delay;
+public TimeSpan Delay
+{
+ get => _delay;
+ set
+ {
+ if (value < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(value));
+ _delay = value;
+ }
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||||||||||||||||||
| using System; | ||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||
| using System.Diagnostics; | ||||||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| namespace Cuemon.Threading | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||
| /// Provides a set of static methods for awaiting asynchronous operations. | ||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||
| public static class Awaiter | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||
| /// Repeatedly invokes the specified asynchronous <paramref name="method"/> until it succeeds or the configured <see cref="AsyncRunOptions.Timeout"/> is reached. | ||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||
| /// <param name="method">The asynchronous function delegate to execute, returning a <see cref="ConditionalValue"/> indicating success or failure.</param> | ||||||||||||||||||||||
| /// <param name="setup">The <see cref="AsyncRunOptions"/> which may be configured.</param> | ||||||||||||||||||||||
| /// <returns> | ||||||||||||||||||||||
| /// A task that represents the asynchronous operation. The task result contains the <see cref="ConditionalValue"/> returned by the last invocation of <paramref name="method"/>, or an unsuccessful value if the timeout is reached. | ||||||||||||||||||||||
| /// </returns> | ||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||
| /// The <paramref name="method"/> is invoked repeatedly with a delay specified by <see cref="AsyncRunOptions.Delay"/> until it returns a successful <see cref="ConditionalValue"/> or the timeout specified by <see cref="AsyncRunOptions.Timeout"/> is reached. | ||||||||||||||||||||||
| /// <br/> | ||||||||||||||||||||||
| /// Potential exceptions thrown by <paramref name="method"/> are caught and collected. If the operation does not succeed before the timeout, <see cref="UnsuccessfulValue"/> will be conditionally initialized:<br/> | ||||||||||||||||||||||
| /// 1: No caught exceptions; initialized with default constructor,<br/> | ||||||||||||||||||||||
| /// 2: One caught exception; initialized with caught exception,<br/> | ||||||||||||||||||||||
| /// 3: Two or more exceptions; initialized with <see cref="AggregateException"/> containing all exceptions. | ||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||
| public static async Task<ConditionalValue> RunUntilSucceededOrTimeoutAsync(Func<Task<ConditionalValue>> method, Action<AsyncRunOptions> setup = null) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| var options = Patterns.Configure(setup); | ||||||||||||||||||||||
|
||||||||||||||||||||||
| var stopwatch = Stopwatch.StartNew(); | ||||||||||||||||||||||
| var exceptions = new List<Exception>(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ConditionalValue conditionalValue = null; | ||||||||||||||||||||||
| while (stopwatch.Elapsed <= options.Timeout) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| try | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| options.CancellationToken.ThrowIfCancellationRequested(); | ||||||||||||||||||||||
| conditionalValue = await method().ConfigureAwait(false); | ||||||||||||||||||||||
| if (conditionalValue.Succeeded) { break; } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| catch (Exception ex) | ||||||||||||||||||||||
|
Comment on lines
+41
to
+45
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. Possible
-conditionalValue = await method().ConfigureAwait(false);
-if (conditionalValue.Succeeded) { break; }
+conditionalValue = await method().ConfigureAwait(false);
+if (conditionalValue?.Succeeded == true) { break; }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| { | ||||||||||||||||||||||
| exceptions.Add(ex); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| await Task.Delay(options.Delay).ConfigureAwait(false); | ||||||||||||||||||||||
|
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. 🛠️ Refactor suggestion Propagate cancellation to the delay
-await Task.Delay(options.Delay).ConfigureAwait(false);
+await Task.Delay(options.Delay, options.CancellationToken).ConfigureAwait(false);This ensures that a cancelled token breaks out immediately. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return (conditionalValue?.Succeeded ?? false) | ||||||||||||||||||||||
| ? conditionalValue | ||||||||||||||||||||||
| : GetUnsuccessfulValue(exceptions); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private static ConditionalValue GetUnsuccessfulValue(IList<Exception> exceptions) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (exceptions.Count == 0) { return new UnsuccessfulValue(); } | ||||||||||||||||||||||
| if (exceptions.Count == 1) { return new UnsuccessfulValue(exceptions[0]); } | ||||||||||||||||||||||
| return new UnsuccessfulValue(new AggregateException(exceptions)); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,125 @@ | ||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||||||||||||
| using System.Threading; | ||||||||||||||||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||||||||||||||||
| using Codebelt.Extensions.Xunit; | ||||||||||||||||||||||||||||||||
| using Xunit; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| namespace Cuemon.Threading | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| public class AwaiterTest : Test | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||
| public async Task RunUntilSucceededOrTimeoutAsync_ShouldReturnOnImmediateSuccess() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| // Arrange | ||||||||||||||||||||||||||||||||
| var callCount = 0; | ||||||||||||||||||||||||||||||||
| Task<ConditionalValue> Method() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| callCount++; | ||||||||||||||||||||||||||||||||
| return Task.FromResult<ConditionalValue>(new SuccessfulValue()); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Act | ||||||||||||||||||||||||||||||||
| var result = await Awaiter.RunUntilSucceededOrTimeoutAsync(Method); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Assert | ||||||||||||||||||||||||||||||||
| Assert.True(result.Succeeded); | ||||||||||||||||||||||||||||||||
| Assert.Equal(1, callCount); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||
| public async Task RunUntilSucceededOrTimeoutAsync_ShouldRetryUntilSuccess() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| // Arrange | ||||||||||||||||||||||||||||||||
| var callCount = 0; | ||||||||||||||||||||||||||||||||
| Task<ConditionalValue> Method() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| callCount++; | ||||||||||||||||||||||||||||||||
| if (callCount < 3) | ||||||||||||||||||||||||||||||||
| return Task.FromResult<ConditionalValue>(new UnsuccessfulValue()); | ||||||||||||||||||||||||||||||||
| return Task.FromResult<ConditionalValue>(new SuccessfulValue()); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Act | ||||||||||||||||||||||||||||||||
| var result = await Awaiter.RunUntilSucceededOrTimeoutAsync(Method, o => | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| o.Timeout = TimeSpan.FromSeconds(2); | ||||||||||||||||||||||||||||||||
| o.Delay = TimeSpan.FromMilliseconds(10); | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Assert | ||||||||||||||||||||||||||||||||
| Assert.True(result.Succeeded); | ||||||||||||||||||||||||||||||||
| Assert.Equal(3, callCount); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||
| public async Task RunUntilSucceededOrTimeoutAsync_ShouldReturnUnsuccessfulOnTimeout_NoExceptions() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| // Arrange | ||||||||||||||||||||||||||||||||
| Task<ConditionalValue> Method() => Task.FromResult<ConditionalValue>(new UnsuccessfulValue()); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Act | ||||||||||||||||||||||||||||||||
| var result = await Awaiter.RunUntilSucceededOrTimeoutAsync(Method, o => | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| o.Timeout = TimeSpan.FromMilliseconds(50); | ||||||||||||||||||||||||||||||||
| o.Delay = TimeSpan.FromMilliseconds(10); | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Assert | ||||||||||||||||||||||||||||||||
| Assert.False(result.Succeeded); | ||||||||||||||||||||||||||||||||
| Assert.Null(result.Failure); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||
| public async Task RunUntilSucceededOrTimeoutAsync_ShouldReturnUnsuccessfulWithSingleException() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| // Arrange | ||||||||||||||||||||||||||||||||
| var ct = new CancellationTokenSource(TimeSpan.FromMilliseconds(25)).Token; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Act | ||||||||||||||||||||||||||||||||
| var result = await Awaiter.RunUntilSucceededOrTimeoutAsync(() => Task.FromResult<ConditionalValue>(new UnsuccessfulValue()), o => | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| o.Timeout = TimeSpan.FromMilliseconds(75); | ||||||||||||||||||||||||||||||||
| o.Delay = TimeSpan.FromMilliseconds(50); | ||||||||||||||||||||||||||||||||
| o.CancellationToken = ct; | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Assert | ||||||||||||||||||||||||||||||||
| Assert.False(result.Succeeded); | ||||||||||||||||||||||||||||||||
| Assert.IsType<OperationCanceledException>(result.Failure); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||
| public async Task RunUntilSucceededOrTimeoutAsync_ShouldReturnUnsuccessfulWithAggregateException() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| // Arrange | ||||||||||||||||||||||||||||||||
| var exceptions = new List<Exception> | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| new InvalidOperationException("fail1"), | ||||||||||||||||||||||||||||||||
| new ArgumentException("fail2") | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| var callCount = 0; | ||||||||||||||||||||||||||||||||
| Task<ConditionalValue> Method() | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| throw exceptions[callCount++]; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+102
to
+110
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. 💡 Verification agent ❓ Verification inconclusive
When the timeout allows more than two iterations, an Consider looping the provided exceptions instead: -Task<ConditionalValue> Method()
-{
- throw exceptions[callCount++];
-}
+Task<ConditionalValue> Method()
+{
+ var ex = exceptions[callCount % exceptions.Count];
+ callCount++;
+ throw ex;
+}This keeps the test deterministic irrespective of how many retries occur. #!/bin/bash
# Locate the AwaiterTest.cs file and inspect the exceptions declaration and Method()
file=$(fd --glob 'AwaiterTest.cs' -t f)
echo "Found file: $file"
echo "---- Head of file ----"
sed -n '1,200p' "$file"
echo "---- Search for exceptions declaration ----"
grep -Rn "exceptions *=" "$file"
echo "---- Search for Method() definition ----"
grep -Rn "Task<ConditionalValue> Method" -n "$file"Cycle through When retries exceed the two predefined failures,
-Task<ConditionalValue> Method()
-{
- throw exceptions[callCount++];
-}
+Task<ConditionalValue> Method()
+{
+ var ex = exceptions[callCount % exceptions.Length];
+ callCount++;
+ throw ex;
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Act | ||||||||||||||||||||||||||||||||
| var result = await Awaiter.RunUntilSucceededOrTimeoutAsync(Method, o => | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| o.Timeout = TimeSpan.FromMilliseconds(50); | ||||||||||||||||||||||||||||||||
| o.Delay = TimeSpan.FromMilliseconds(10); | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Assert | ||||||||||||||||||||||||||||||||
| Assert.False(result.Succeeded); | ||||||||||||||||||||||||||||||||
| Assert.IsType<AggregateException>(result.Failure); | ||||||||||||||||||||||||||||||||
| var agg = (AggregateException)result.Failure; | ||||||||||||||||||||||||||||||||
| Assert.Contains(exceptions[0], agg.InnerExceptions); | ||||||||||||||||||||||||||||||||
| Assert.Contains(exceptions[1], agg.InnerExceptions); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate
TimeoutandDelayat construction timeA negative
Timeout/Delay(or aDelaygreater thanTimeout) will makeAwaiterbehave unpredictably or throw deep insideTask.Delay.Sanitise once in the ctor to fail-fast.
Call
Validate()at the end of the ctor and from both property setters (see next comment).🤖 Prompt for AI Agents