Skip to content

Commit b0118be

Browse files
authored
Add Task.WaitAll overload for IEnumerable<Task> (#100569)
* Add Task.WaitAll overload for `IEnumerable<Task>` * Disable tests on browser
1 parent 9068070 commit b0118be

File tree

10 files changed

+113
-36
lines changed

10 files changed

+113
-36
lines changed

src/libraries/System.Collections.Concurrent/tests/ConcurrentQueueTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public void MultipleProducerConsumer_AllItemsTransferred(int producers, int cons
130130
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default));
131131
}
132132

133-
Task.WaitAll(tasks.ToArray());
133+
Task.WaitAll(tasks);
134134

135135
Assert.Equal(producers * (itemsPerProducer * (itemsPerProducer + 1) / 2), sum);
136136
}

src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void MultipleProcesses_ParallelStartKillWait()
8686
p.WaitForExit(WaitInMS);
8787
}
8888
};
89-
Task.WaitAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work)).ToArray());
89+
Task.WaitAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work)));
9090
}
9191

9292
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
@@ -108,7 +108,7 @@ public async Task MultipleProcesses_ParallelStartKillWaitAsync()
108108
}
109109
};
110110

111-
await Task.WhenAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work)).ToArray());
111+
await Task.WhenAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work)));
112112
}
113113

114114
[Theory]

src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public static void CreateMultipleServers_ConnectMultipleClients_MultipleThreads(
131131
}));
132132
}
133133

134-
Task.WaitAll(tasks.ToArray());
134+
Task.WaitAll(tasks);
135135
}
136136

137137
[Theory]

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4681,14 +4681,13 @@ internal void RemoveContinuation(object continuationObject) // could be TaskCont
46814681
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
46824682
public static void WaitAll(params Task[] tasks)
46834683
{
4684-
#if DEBUG
4685-
bool waitResult =
4686-
#endif
4687-
WaitAllCore(tasks, Timeout.Infinite, default);
4684+
if (tasks is null)
4685+
{
4686+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
4687+
}
46884688

4689-
#if DEBUG
4689+
bool waitResult = WaitAllCore(tasks, Timeout.Infinite, default);
46904690
Debug.Assert(waitResult, "expected wait to succeed");
4691-
#endif
46924691
}
46934692

46944693
/// <summary>
@@ -4730,6 +4729,11 @@ public static bool WaitAll(Task[] tasks, TimeSpan timeout)
47304729
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
47314730
}
47324731

4732+
if (tasks is null)
4733+
{
4734+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
4735+
}
4736+
47334737
return WaitAllCore(tasks, (int)totalMilliseconds, default);
47344738
}
47354739

@@ -4763,6 +4767,11 @@ public static bool WaitAll(Task[] tasks, TimeSpan timeout)
47634767
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
47644768
public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
47654769
{
4770+
if (tasks is null)
4771+
{
4772+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
4773+
}
4774+
47664775
return WaitAllCore(tasks, millisecondsTimeout, default);
47674776
}
47684777

@@ -4792,6 +4801,11 @@ public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
47924801
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
47934802
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
47944803
{
4804+
if (tasks is null)
4805+
{
4806+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
4807+
}
4808+
47954809
WaitAllCore(tasks, Timeout.Infinite, cancellationToken);
47964810
}
47974811

@@ -4831,18 +4845,48 @@ public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
48314845
/// </exception>
48324846
[UnsupportedOSPlatform("browser")]
48334847
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4834-
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) =>
4835-
WaitAllCore(tasks, millisecondsTimeout, cancellationToken);
4848+
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
4849+
{
4850+
if (tasks is null)
4851+
{
4852+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
4853+
}
48364854

4837-
// Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger
4838-
// to be able to see the method on the stack and inspect arguments).
4855+
return WaitAllCore(tasks, millisecondsTimeout, cancellationToken);
4856+
}
4857+
4858+
/// <summary>Waits for all of the provided <see cref="Task"/> objects to complete execution unless the wait is cancelled.</summary>
4859+
/// <param name="tasks">An <see cref="IEnumerable{T}"/> of Task instances on which to wait.</param>
4860+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.</param>
4861+
/// <exception cref="ArgumentNullException">The <paramref name="tasks"/> argument is null.</exception>
4862+
/// <exception cref="ArgumentException">The <paramref name="tasks"/> argument contains a null element.</exception>
4863+
/// <exception cref="ObjectDisposedException">One or more of the <see cref="Task"/> objects in tasks has been disposed.</exception>
4864+
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> was canceled.</exception>
4865+
/// <exception cref="AggregateException">
4866+
/// At least one of the <see cref="Task"/> instances was canceled. If a task was canceled, the <see cref="AggregateException"/>
4867+
/// contains an <see cref="OperationCanceledException"/> in its <see cref="AggregateException.InnerExceptions"/> collection.
4868+
/// </exception>
48394869
[UnsupportedOSPlatform("browser")]
4840-
private static bool WaitAllCore(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
4870+
public static void WaitAll(IEnumerable<Task> tasks, CancellationToken cancellationToken = default)
48414871
{
4842-
if (tasks == null)
4872+
if (tasks is null)
48434873
{
48444874
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
48454875
}
4876+
4877+
ReadOnlySpan<Task> span =
4878+
tasks is List<Task> list ? CollectionsMarshal.AsSpan(list) :
4879+
tasks is Task[] array ? array :
4880+
CollectionsMarshal.AsSpan(new List<Task>(tasks));
4881+
4882+
WaitAllCore(span, Timeout.Infinite, cancellationToken);
4883+
}
4884+
4885+
// Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger
4886+
// to be able to see the method on the stack and inspect arguments).
4887+
[UnsupportedOSPlatform("browser")]
4888+
private static bool WaitAllCore(ReadOnlySpan<Task> tasks, int millisecondsTimeout, CancellationToken cancellationToken)
4889+
{
48464890
if (millisecondsTimeout < -1)
48474891
{
48484892
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
@@ -4919,11 +4963,6 @@ private static bool WaitAllCore(Task[] tasks, int millisecondsTimeout, Cancellat
49194963
if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1);
49204964
}
49214965
}
4922-
4923-
// We need to prevent the tasks array from being GC'ed until we come out of the wait.
4924-
// This is necessary so that the Parallel Debugger can traverse it during the long wait and
4925-
// deduce waiter/waitee relationships
4926-
GC.KeepAlive(tasks);
49274966
}
49284967

49294968
// Now that we're done and about to exit, if the wait completed and if we have

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15307,6 +15307,8 @@ public void Wait(System.Threading.CancellationToken cancellationToken) { }
1530715307
public bool Wait(System.TimeSpan timeout) { throw null; }
1530815308
public bool Wait(System.TimeSpan timeout, System.Threading.CancellationToken cancellationToken) { throw null; }
1530915309
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
15310+
public static void WaitAll(System.Collections.Generic.IEnumerable<Task> tasks, System.Threading.CancellationToken cancellationToken = default) { }
15311+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
1531015312
public static void WaitAll(params System.Threading.Tasks.Task[] tasks) { }
1531115313
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
1531215314
public static bool WaitAll(System.Threading.Tasks.Task[] tasks, int millisecondsTimeout) { throw null; }

src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ public void CurrentManagedThreadId_DifferentForActiveThreads()
6363
{
6464
var ids = new HashSet<int>();
6565
Barrier b = new Barrier(10);
66-
Task.WaitAll((from i in Enumerable.Range(0, b.ParticipantCount)
67-
select Task.Factory.StartNew(() =>
68-
{
69-
b.SignalAndWait();
70-
lock (ids) ids.Add(Environment.CurrentManagedThreadId);
71-
b.SignalAndWait();
72-
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray());
66+
Task.WaitAll(from i in Enumerable.Range(0, b.ParticipantCount)
67+
select Task.Factory.StartNew(() =>
68+
{
69+
b.SignalAndWait();
70+
lock (ids) ids.Add(Environment.CurrentManagedThreadId);
71+
b.SignalAndWait();
72+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default));
7373
Assert.Equal(b.ParticipantCount, ids.Count);
7474
}
7575

src/libraries/System.Runtime/tests/System.Security.SecureString.Tests/SecureStringTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ public static void Grow_Large()
499499
break;
500500
}
501501
}
502-
})).ToArray());
502+
})));
503503
}
504504
}
505505

src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CESchedulerPairTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ public static void TestMaxItemsPerTask(int maxConcurrency, int maxItemsPerTask,
244244
}
245245

246246
//finally wait for all of the tasks, to ensure they all executed properly
247-
Task.WaitAll(taskList.ToArray());
247+
Task.WaitAll(taskList);
248248

249249
if (!completeBeforeTaskWait)
250250
{
@@ -292,7 +292,7 @@ public static void TestLowerConcurrencyLevel()
292292

293293
//finally unblock the blocjedTask and wait for all of the tasks, to ensure they all executed properly
294294
blockReaderTaskEvent.Set();
295-
Task.WaitAll(taskList.ToArray());
295+
Task.WaitAll(taskList);
296296
}
297297

298298
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
@@ -325,7 +325,7 @@ public static void TestConcurrentBlockage()
325325
}
326326

327327
blockExclusiveTaskEvent.Set();
328-
Task.WaitAll(taskList.ToArray());
328+
Task.WaitAll(taskList);
329329
}
330330

331331
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
@@ -482,7 +482,7 @@ public static void TestSchedulerNesting()
482482
}
483483

484484
// Wait for all tasks to complete, then complete the schedulers
485-
Task.WaitAll(tasks.ToArray());
485+
Task.WaitAll(tasks);
486486
foreach (var cesp in cesps)
487487
{
488488
cesp.Complete();

src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ public TaskWaitAllAnyTest(TestParameters_WaitAllAny parameters)
220220
/// <summary>
221221
/// The method that will run the scenario
222222
/// </summary>
223-
/// <returns></returns>
224223
internal void RealRun()
225224
{
226225
//create the tasks
@@ -325,7 +324,6 @@ private void CreateTask()
325324
/// - the returned index form WaitAny should correspond to a completed task
326325
/// - in case of Cancelled and Exception tests the right exceptions should be got for WaitAll
327326
/// </summary>
328-
/// <returns></returns>
329327
private void Verify()
330328
{
331329
// verification for WaitAll
@@ -1255,5 +1253,43 @@ public static void TaskWaitAllAny58()
12551253
TaskWaitAllAnyTest test = new TaskWaitAllAnyTest(parameters);
12561254
test.RealRun();
12571255
}
1256+
1257+
[Fact]
1258+
public static void TaskWaitAll_Enumerable_InvalidArguments()
1259+
{
1260+
AssertExtensions.Throws<ArgumentNullException>("tasks", () => Task.WaitAll((IEnumerable<Task>)null));
1261+
AssertExtensions.Throws<ArgumentException>("tasks", () => Task.WaitAll((IEnumerable<Task>)[null]));
1262+
AssertExtensions.Throws<ArgumentException>("tasks", () => Task.WaitAll((IEnumerable<Task>)[Task.CompletedTask, null]));
1263+
AssertExtensions.Throws<ArgumentException>("tasks", () => Task.WaitAll((IEnumerable<Task>)[null, Task.CompletedTask]));
1264+
}
1265+
1266+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
1267+
public static void TaskWaitAll_Enumerable_Canceled()
1268+
{
1269+
var tcs = new TaskCompletionSource();
1270+
1271+
Assert.Throws<OperationCanceledException>(() => Task.WaitAll((IEnumerable<Task>)[tcs.Task], new CancellationToken(true)));
1272+
1273+
using var cts = new CancellationTokenSource(1);
1274+
Assert.Throws<OperationCanceledException>(() => Task.WaitAll((IEnumerable<Task>)[Task.CompletedTask, tcs.Task], cts.Token));
1275+
}
1276+
1277+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
1278+
public static void TaskWaitAll_Enumerable_AllComplete()
1279+
{
1280+
Task.WaitAll((IEnumerable<Task>)[]);
1281+
Task.WaitAll((IEnumerable<Task>)[Task.CompletedTask]);
1282+
Task.WaitAll((IEnumerable<Task>)[Task.CompletedTask, Task.CompletedTask]);
1283+
1284+
Task.WaitAll((IEnumerable<Task>)(Task[])[Task.CompletedTask, Task.CompletedTask]);
1285+
1286+
Task.WaitAll((List<Task>)[]);
1287+
Task.WaitAll((List<Task>)[Task.CompletedTask]);
1288+
Task.WaitAll((List<Task>)[Task.CompletedTask, Task.CompletedTask]);
1289+
1290+
Task.WaitAll((IEnumerable<Task>)[Task.Delay(1)]);
1291+
Task.WaitAll((List<Task>)[Task.Delay(1)]);
1292+
Task.WaitAll((IEnumerable<Task>)[Task.Delay(1), Task.CompletedTask, Task.Delay(1), Task.CompletedTask, Task.Delay(1)]);
1293+
}
12581294
}
12591295
}

src/libraries/System.Threading/tests/InterlockedTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ public void MemoryBarrierProcessWide()
810810
}
811811
}));
812812
}
813-
Task.WaitAll(threads.ToArray());
813+
Task.WaitAll(threads);
814814
Assert.Equal(1000*1000, count);
815815
}
816816

0 commit comments

Comments
 (0)