Skip to content

Commit 4e137fb

Browse files
feat: added support for AggregateException
1 parent d67e727 commit 4e137fb

File tree

2 files changed

+44
-11
lines changed

2 files changed

+44
-11
lines changed

src/WouterVanRanst.Utils.Tests/TaskExtensionsTests.cs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public async Task ExecuteTasksWithCancellationAsync_ShouldCancelAllTasks_WhenCan
1919
var t = TaskExtensions.WhenAllWithCancellationAsync(new[] { task1, task2 }, cancellationTokenSource);
2020

2121
// Cancel after a short delay
22-
cancellationTokenSource.CancelAfter(100);
22+
cancellationTokenSource.CancelAfter(10);
2323

2424
// Assert
2525
await FluentActions
@@ -38,7 +38,7 @@ public async Task ExecuteTasksWithCancellationAsync_ShouldCancelAllOtherTasks_Wh
3838
var cancellationTokenSource = new CancellationTokenSource();
3939
var cancellationToken = cancellationTokenSource.Token;
4040

41-
var task1 = CreateFaultedTask("Task 1 failed", 500);
41+
var task1 = CreateFaultedTask("Task 1 failed", 50);
4242
var task2 = CreateLongRunningTask(cancellationToken);
4343

4444
// Act
@@ -92,8 +92,8 @@ public async Task ExecuteTasksWithCancellationAsync_ShouldCancelAllOtherTasks_Wh
9292
var cancellationTokenSource = new CancellationTokenSource();
9393
var cancellationToken = cancellationTokenSource.Token;
9494

95-
var task1 = CreateFaultedTask("Task 1 failed", 500);
96-
var task2 = CreateFaultedTask("Task 2 failed", 700);
95+
var task1 = CreateFaultedTask("Task 1 failed", 50);
96+
var task2 = CreateFaultedTask("Task 2 failed", 70);
9797
var task3 = CreateLongRunningTask(cancellationToken);
9898

9999
// Act
@@ -141,7 +141,7 @@ public async Task ExecuteTasksWithCancellationAsync_ShouldCancelTasks_WhenExtern
141141
var cancellationTokenSource = new CancellationTokenSource();
142142
var cancellationToken = cancellationTokenSource.Token;
143143

144-
var task1 = CreateFaultedTask("Task 1 failed", 500);
144+
var task1 = CreateFaultedTask("Task 1 failed", 50);
145145
var task2 = CreateLongRunningTask(cancellationToken);
146146

147147
// Act: Trigger external cancellation after 500ms
@@ -155,8 +155,32 @@ public async Task ExecuteTasksWithCancellationAsync_ShouldCancelTasks_WhenExtern
155155
cancellationToken.IsCancellationRequested.Should().BeTrue(); // Ensure cancellation was requested
156156
}
157157

158+
[Fact]
159+
public async Task ExecuteTasksWithCancellationAsync_ShouldThrowAggregateException_WhenMultipleTasksFault()
160+
{
161+
// Arrange
162+
var cancellationTokenSource = new CancellationTokenSource();
163+
var cancellationToken = cancellationTokenSource.Token;
164+
165+
// Create tasks: Two faulted tasks and one long-running task
166+
var task1 = CreateFaultedTask("Task 1 failed", 0);
167+
var task2 = CreateFaultedTask("Task 2 failed", 0);
168+
var task3 = CreateLongRunningTask(cancellationToken); // Long-running task
158169

170+
// Act
171+
Func<Task> act = async () => await TaskExtensions.WhenAllWithCancellationAsync([task1, task2, task3], cancellationTokenSource);
172+
173+
// Assert: Expect an AggregateException with both task failures
174+
var exception = await act.Should().ThrowAsync<AggregateException>();
175+
exception.Which.InnerExceptions.Should().Contain(e => e.Message == "Task 1 failed");
176+
exception.Which.InnerExceptions.Should().Contain(e => e.Message == "Task 2 failed");
177+
178+
// Verify that the long-running task was canceled
179+
task3.Status.Should().Be(TaskStatus.Canceled);
180+
cancellationTokenSource.IsCancellationRequested.Should().BeTrue();
181+
}
159182

183+
160184
private static Task CreateLongRunningTask(CancellationToken cancellationToken)
161185
{
162186
return Task.Run(async () =>
@@ -173,7 +197,7 @@ private static Task CreateLongRunningTask(CancellationToken cancellationToken)
173197
}, cancellationToken);
174198
}
175199

176-
private static Task CreateFaultedTask(string exceptionMessage, int delayMs = 500)
200+
private static Task CreateFaultedTask(string exceptionMessage, int delayMs)
177201
{
178202
return Task.Run(async () =>
179203
{

src/WouterVanRanst.Utils/Extensions/TaskExtensions.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
public static class TaskExtensions
44
{
5-
public static async Task WhenAllWithCancellationAsync(IEnumerable<Task> tasks, CancellationTokenSource globalCancellationTokenSource)
5+
public static async Task WhenAllWithCancellationAsync(IEnumerable<Task> tasks, CancellationTokenSource cancellationTokenSource)
66
{
7+
tasks = tasks.ToArray();
8+
79
if (!tasks.Any())
810
{
911
return;
1012
}
1113

14+
Task? t = default;
15+
1216
try
1317
{
1418
// Attach faulted continuations to each task to trigger cancellation when any task fails
@@ -19,19 +23,24 @@ public static async Task WhenAllWithCancellationAsync(IEnumerable<Task> tasks, C
1923
if (t.IsFaulted)
2024
{
2125
Console.WriteLine("A task has faulted, cancelling all other tasks.");
22-
globalCancellationTokenSource.Cancel();
26+
cancellationTokenSource.Cancel();
2327
}
2428
}, TaskContinuationOptions.OnlyOnFaulted);
2529
}
2630

2731
// Await the completion of all tasks, letting cancellation propagate as necessary
28-
await Task.WhenAll(tasks);
32+
t = Task.WhenAll(tasks);
33+
await t;
2934
}
3035
catch
3136
{
3237
// If any task faults, ensure cancellation is triggered
33-
globalCancellationTokenSource.Cancel();
34-
throw;
38+
await cancellationTokenSource.CancelAsync();
39+
40+
if (t?.Exception is not null)
41+
throw t.Exception.Flatten();
42+
else
43+
throw;
3544
}
3645
}
3746
}

0 commit comments

Comments
 (0)