From a13bfa3e75e42012d738b1e38a120d3592795e05 Mon Sep 17 00:00:00 2001 From: Nikolay Zdravkov Date: Tue, 21 Apr 2026 12:11:40 +0300 Subject: [PATCH] perf: specialize Task.WhenAny for 3 and 4 tasks Mirrors the existing TwoTaskWhenAnyPromise pattern to eliminate the Task[] defensive-copy allocation for Count=3 and Count=4. Count>=5 unchanged. Closes #126748 --- .../src/System/Threading/Tasks/Task.cs | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 520c0642c4e1fd..1c39ff9bfbcd68 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -6629,6 +6629,16 @@ private static Task WhenAnyCore(ReadOnlySpan tasks) where T return WhenAny(tasks[0], tasks[1]); } + if (tasks.Length == 3) + { + return WhenAny(tasks[0], tasks[1], tasks[2]); + } + + if (tasks.Length == 4) + { + return WhenAny(tasks[0], tasks[1], tasks[2], tasks[3]); + } + if (tasks.IsEmpty) { ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); @@ -6765,6 +6775,211 @@ public void Invoke(Task completingTask) public bool InvokeMayRunArbitraryCode => true; } + private static Task WhenAny(TTask task1, TTask task2, TTask task3) where TTask : Task + { + ArgumentNullException.ThrowIfNull(task1); + ArgumentNullException.ThrowIfNull(task2); + ArgumentNullException.ThrowIfNull(task3); + + return + task1.IsCompleted ? FromResult(task1) : + task2.IsCompleted ? FromResult(task2) : + task3.IsCompleted ? FromResult(task3) : + new ThreeTaskWhenAnyPromise(task1, task2, task3); + } + + /// A promise type used by WhenAny to wait on exactly three tasks. + /// Specifies the type of the task. + /// + /// This has essentially the same logic as , but optimized + /// for three tasks rather than any number. + /// + private sealed class ThreeTaskWhenAnyPromise : Task, ITaskCompletionAction where TTask : Task + { + private TTask? _task1, _task2, _task3; + + /// Instantiate the promise and register it with each task as a completion action. + public ThreeTaskWhenAnyPromise(TTask task1, TTask task2, TTask task3) + { + Debug.Assert(task1 != null && task2 != null && task3 != null); + _task1 = task1; + _task2 = task2; + _task3 = task3; + + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationBegin(this.Id, "Task.WhenAny", 0); + } + + if (s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + + task1.AddCompletionAction(this); + task2.AddCompletionAction(this); + task3.AddCompletionAction(this); + + if (task1.IsCompleted) + { + // If task1 has already completed, Invoke may have tried to remove the continuation from + // each task before task2 and task3 added the continuation, in which case they're now referencing the + // already completed continuation. To deal with that race condition, explicitly check + // and remove the continuation here. + task2.RemoveContinuation(this); + task3.RemoveContinuation(this); + } + else if (task2.IsCompleted) + { + task3.RemoveContinuation(this); + } + } + + /// Completes this task when one of the constituent tasks completes. + public void Invoke(Task completingTask) + { + Task? task1; + if ((task1 = Interlocked.Exchange(ref _task1, null)) != null) + { + Task? task2 = _task2; + Task? task3 = _task3; + _task2 = null; + _task3 = null; + + Debug.Assert(task1 != null && task2 != null && task3 != null); + + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationRelation(this.Id, CausalityRelation.Choice); + TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Completed); + } + + if (s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this); + } + + if (!task1.IsCompleted) task1.RemoveContinuation(this); + if (!task2.IsCompleted) task2.RemoveContinuation(this); + if (!task3.IsCompleted) task3.RemoveContinuation(this); + + bool success = TrySetResult((TTask)completingTask); + Debug.Assert(success, "Only one task should have gotten to this point, and thus this must be successful."); + } + } + + public bool InvokeMayRunArbitraryCode => true; + } + + private static Task WhenAny(TTask task1, TTask task2, TTask task3, TTask task4) where TTask : Task + { + ArgumentNullException.ThrowIfNull(task1); + ArgumentNullException.ThrowIfNull(task2); + ArgumentNullException.ThrowIfNull(task3); + ArgumentNullException.ThrowIfNull(task4); + + return + task1.IsCompleted ? FromResult(task1) : + task2.IsCompleted ? FromResult(task2) : + task3.IsCompleted ? FromResult(task3) : + task4.IsCompleted ? FromResult(task4) : + new FourTaskWhenAnyPromise(task1, task2, task3, task4); + } + + /// A promise type used by WhenAny to wait on exactly four tasks. + /// Specifies the type of the task. + /// + /// This has essentially the same logic as , but optimized + /// for four tasks rather than any number. + /// + private sealed class FourTaskWhenAnyPromise : Task, ITaskCompletionAction where TTask : Task + { + private TTask? _task1, _task2, _task3, _task4; + + /// Instantiate the promise and register it with each task as a completion action. + public FourTaskWhenAnyPromise(TTask task1, TTask task2, TTask task3, TTask task4) + { + Debug.Assert(task1 != null && task2 != null && task3 != null && task4 != null); + _task1 = task1; + _task2 = task2; + _task3 = task3; + _task4 = task4; + + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationBegin(this.Id, "Task.WhenAny", 0); + } + + if (s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + + task1.AddCompletionAction(this); + task2.AddCompletionAction(this); + task3.AddCompletionAction(this); + task4.AddCompletionAction(this); + + if (task1.IsCompleted) + { + // If task1 has already completed, Invoke may have tried to remove the continuation from + // each task before task2, task3 and task4 added the continuation, in which case they're now referencing the + // already completed continuation. To deal with that race condition, explicitly check + // and remove the continuation here. + task2.RemoveContinuation(this); + task3.RemoveContinuation(this); + task4.RemoveContinuation(this); + } + else if (task2.IsCompleted) + { + task3.RemoveContinuation(this); + task4.RemoveContinuation(this); + } + else if (task3.IsCompleted) + { + task4.RemoveContinuation(this); + } + } + + /// Completes this task when one of the constituent tasks completes. + public void Invoke(Task completingTask) + { + Task? task1; + if ((task1 = Interlocked.Exchange(ref _task1, null)) != null) + { + Task? task2 = _task2; + Task? task3 = _task3; + Task? task4 = _task4; + _task2 = null; + _task3 = null; + _task4 = null; + + Debug.Assert(task1 != null && task2 != null && task3 != null && task4 != null); + + if (TplEventSource.Log.IsEnabled()) + { + TplEventSource.Log.TraceOperationRelation(this.Id, CausalityRelation.Choice); + TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Completed); + } + + if (s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this); + } + + if (!task1.IsCompleted) task1.RemoveContinuation(this); + if (!task2.IsCompleted) task2.RemoveContinuation(this); + if (!task3.IsCompleted) task3.RemoveContinuation(this); + if (!task4.IsCompleted) task4.RemoveContinuation(this); + + bool success = TrySetResult((TTask)completingTask); + Debug.Assert(success, "Only one task should have gotten to this point, and thus this must be successful."); + } + } + + public bool InvokeMayRunArbitraryCode => true; + } + /// /// Creates a task that will complete when any of the supplied tasks have completed. ///