diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs index 23cc6fd44ef7b..1f6fb5f62337e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs @@ -1626,7 +1626,7 @@ internal static Task ContinueWhenAllImpl(Task.CWAllFuncDelegate, + static (starter, continuationFunction) => ((Func[], TResult>)continuationFunction!)(starter.Result), continuationFunction, scheduler, cancellationToken, continuationOptions); } else @@ -1634,7 +1634,11 @@ internal static Task ContinueWhenAllImpl(Task.CWAllActionDelegate, + static (starter, continuationAction) => + { + ((Action[]>)continuationAction!)(starter.Result); + return default(TResult)!; + }, continuationAction, scheduler, cancellationToken, continuationOptions); } } @@ -2026,7 +2030,7 @@ internal static Task ContinueWhenAnyImpl(Task starter = TaskFactory.CommonCWAnyLogic(tasks); + Task> starter = TaskFactory.CommonCWAnyLogic(tasks); // Bail early if cancellation has been requested. if (cancellationToken.IsCancellationRequested @@ -2040,64 +2044,20 @@ internal static Task ContinueWhenAnyImpl(Task.CWAnyFuncDelegate, + static (starter, continuationFunction) => ((Func, TResult>)continuationFunction!)(starter.Result), continuationFunction, scheduler, cancellationToken, continuationOptions); } else { Debug.Assert(continuationAction != null); return starter.ContinueWith( - GenericDelegateCache.CWAnyActionDelegate, + static (starter, continuationAction) => + { + ((Action>)continuationAction!)(starter.Result); + return default(TResult)!; + }, continuationAction, scheduler, cancellationToken, continuationOptions); } } } - - // For the ContinueWhenAnyImpl/ContinueWhenAllImpl methods that are generic on TAntecedentResult, - // the compiler won't cache the internal ContinueWith delegate because it is generic on both - // TAntecedentResult and TResult. The GenericDelegateCache serves as a cache for those delegates. - internal static class GenericDelegateCache - { - // ContinueWith delegate for TaskFactory.ContinueWhenAnyImpl(non-null continuationFunction) - internal static Func, object?, TResult> CWAnyFuncDelegate = - static (Task wrappedWinner, object? state) => - { - Debug.Assert(state is Func, TResult>); - var func = (Func, TResult>)state; - var arg = (Task)wrappedWinner.Result; - return func(arg); - }; - - // ContinueWith delegate for TaskFactory.ContinueWhenAnyImpl(non-null continuationAction) - internal static Func, object?, TResult> CWAnyActionDelegate = - static (Task wrappedWinner, object? state) => - { - Debug.Assert(state is Action>); - var action = (Action>)state; - var arg = (Task)wrappedWinner.Result; - action(arg); - return default!; - }; - - // ContinueWith delegate for TaskFactory.ContinueWhenAllImpl(non-null continuationFunction) - internal static Func[]>, object?, TResult> CWAllFuncDelegate = - static (Task[]> wrappedAntecedents, object? state) => - { - wrappedAntecedents.NotifyDebuggerOfWaitCompletionIfNecessary(); - Debug.Assert(state is Func[], TResult>); - var func = (Func[], TResult>)state; - return func(wrappedAntecedents.Result); - }; - - // ContinueWith delegate for TaskFactory.ContinueWhenAllImpl(non-null continuationAction) - internal static Func[]>, object?, TResult> CWAllActionDelegate = - static (Task[]> wrappedAntecedents, object? state) => - { - wrappedAntecedents.NotifyDebuggerOfWaitCompletionIfNecessary(); - Debug.Assert(state is Action[]>); - var action = (Action[]>)state; - action(wrappedAntecedents.Result); - return default!; - }; - } } 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 cae33611d5d98..9211dd6e0fd88 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 @@ -6119,12 +6119,14 @@ public static Task WhenAll(IEnumerable> tasks) // Skip a List allocation/copy if tasks is a collection if (tasks is ICollection> taskCollection) { - int index = 0; taskArray = new Task[taskCollection.Count]; - foreach (Task task in tasks) + taskCollection.CopyTo(taskArray, 0); + foreach (Task task in taskArray) { - if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); - taskArray[index++] = task; + if (task is null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + } } return InternalWhenAll(taskArray); } @@ -6180,12 +6182,13 @@ public static Task WhenAll(params Task[] tasks) int taskCount = tasks.Length; if (taskCount == 0) return InternalWhenAll(tasks); // small optimization in the case of an empty task array - Task[] tasksCopy = new Task[taskCount]; - for (int i = 0; i < taskCount; i++) + Task[] tasksCopy = (Task[])tasks.Clone(); + foreach (Task task in tasksCopy) { - Task task = tasks[i]; - if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); - tasksCopy[i] = task; + if (task is null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + } } // Delegate the rest to InternalWhenAll() @@ -6336,30 +6339,44 @@ public void Invoke(Task ignored) /// public static Task WhenAny(params Task[] tasks) { - if (tasks == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); - } + ArgumentNullException.ThrowIfNull(tasks); + return WhenAny((ReadOnlySpan)tasks); + } + + /// + /// Creates a task that will complete when any of the supplied tasks have completed. + /// + /// The tasks to wait on for completion. + /// A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed. + /// + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// + /// + /// The array contained a null task, or was empty. + /// + private static Task WhenAny(ReadOnlySpan tasks) where TTask : Task + { if (tasks.Length == 2) { return WhenAny(tasks[0], tasks[1]); } - if (tasks.Length == 0) + if (tasks.IsEmpty) { ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); } - // Make a defensive copy, as the user may manipulate the tasks array + // Make a defensive copy, as the user may manipulate the input collection // after we return but before the WhenAny asynchronously completes. - int taskCount = tasks.Length; - Task[] tasksCopy = new Task[taskCount]; - for (int i = 0; i < taskCount; i++) + TTask[] tasksCopy = tasks.ToArray(); + foreach (TTask task in tasksCopy) { - Task task = tasks[i]; - if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); - tasksCopy[i] = task; + if (task is null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + } } // Previously implemented CommonCWAnyLogic() can handle the rest @@ -6377,7 +6394,21 @@ public static Task WhenAny(params Task[] tasks) /// /// The or argument was null. /// - public static Task WhenAny(Task task1, Task task2) + public static Task WhenAny(Task task1, Task task2) => + WhenAny(task1, task2); + + /// Creates a task that will complete when either of the supplied tasks have completed. + /// The first task to wait on for completion. + /// The second task to wait on for completion. + /// A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed. + /// + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// + /// + /// The or argument was null. + /// + private static Task WhenAny(TTask task1, TTask task2) where TTask : Task { ArgumentNullException.ThrowIfNull(task1); ArgumentNullException.ThrowIfNull(task2); @@ -6385,13 +6416,13 @@ public static Task WhenAny(Task task1, Task task2) return task1.IsCompleted ? FromResult(task1) : task2.IsCompleted ? FromResult(task2) : - new TwoTaskWhenAnyPromise(task1, task2); + new TwoTaskWhenAnyPromise(task1, task2); } /// A promise type used by WhenAny to wait on exactly two tasks. /// Specifies the type of the task. /// - /// This has essentially the same logic as , but optimized + /// This has essentially the same logic as , but optimized /// for two tasks rather than any number. Exactly two tasks has shown to be the most common use-case by far. /// private sealed class TwoTaskWhenAnyPromise : Task, ITaskCompletionAction where TTask : Task @@ -6483,41 +6514,71 @@ public void Invoke(Task completingTask) /// /// The collection contained a null task, or was empty. /// - public static Task WhenAny(IEnumerable tasks) + public static Task WhenAny(IEnumerable tasks) => + WhenAny(tasks); + + /// + /// Creates a task that will complete when any of the supplied tasks have completed. + /// + /// The tasks to wait on for completion. + /// A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed. + /// + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// + /// + /// The collection contained a null task, or was empty. + /// + private static Task WhenAny(IEnumerable tasks) where TTask : Task { // Skip a List allocation/copy if tasks is a collection - if (tasks is ICollection taskCollection) + if (tasks is ICollection tasksAsCollection) { - // Take a more efficient path if tasks is actually an array - if (tasks is Task[] taskArray) + // Take a more efficient path if tasks is actually a list or an array. Arrays are a bit less common, + // since if argument was strongly-typed as an array, it would have bound to the array-based overload. + if (tasks is List tasksAsList) { - return WhenAny(taskArray); + return WhenAny((ReadOnlySpan)CollectionsMarshal.AsSpan(tasksAsList)); + } + if (tasks is TTask[] tasksAsArray) + { + return WhenAny((ReadOnlySpan)tasksAsArray); } - int count = taskCollection.Count; + int count = tasksAsCollection.Count; if (count <= 0) { ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); } - int index = 0; - taskArray = new Task[count]; - foreach (Task task in tasks) + var taskArray = new TTask[count]; + tasksAsCollection.CopyTo(taskArray, 0); + foreach (TTask task in taskArray) { - if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); - taskArray[index++] = task; + if (task is null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + } } + return TaskFactory.CommonCWAnyLogic(taskArray); } - if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + if (tasks is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } // Make a defensive copy, as the user may manipulate the tasks collection // after we return but before the WhenAny asynchronously completes. - List taskList = new List(); - foreach (Task task in tasks) + var taskList = new List(); + foreach (TTask task in tasks) { - if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + if (task is null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + } + taskList.Add(task); } @@ -6547,21 +6608,9 @@ public static Task WhenAny(IEnumerable tasks) /// public static Task> WhenAny(params Task[] tasks) { - // We would just like to do this: - // return (Task>) WhenAny( (Task[]) tasks); - // but classes are not covariant to enable casting Task to Task>. + ArgumentNullException.ThrowIfNull(tasks); - if (tasks != null && tasks.Length == 2) - { - return WhenAny(tasks[0], tasks[1]); - } - - // Call WhenAny(Task[]) for basic functionality - Task intermediate = WhenAny((Task[])tasks!); - - // Return a continuation task with the correct result type - return intermediate.ContinueWith(Task.TaskWhenAnyCast.Value, default, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + return WhenAny((ReadOnlySpan>)tasks); } /// Creates a task that will complete when either of the supplied tasks have completed. @@ -6575,16 +6624,8 @@ public static Task> WhenAny(params Task[] tasks) /// /// The or argument was null. /// - public static Task> WhenAny(Task task1, Task task2) - { - ArgumentNullException.ThrowIfNull(task1); - ArgumentNullException.ThrowIfNull(task2); - - return - task1.IsCompleted ? FromResult(task1) : - task2.IsCompleted ? FromResult(task2) : - new TwoTaskWhenAnyPromise>(task1, task2); - } + public static Task> WhenAny(Task task1, Task task2) => + WhenAny>(task1, task2); /// /// Creates a task that will complete when any of the supplied tasks have completed. @@ -6601,19 +6642,8 @@ public static Task> WhenAny(Task task1, Task /// The collection contained a null task, or was empty. /// - public static Task> WhenAny(IEnumerable> tasks) - { - // We would just like to do this: - // return (Task>) WhenAny( (IEnumerable) tasks); - // but classes are not covariant to enable casting Task to Task>. - - // Call WhenAny(IEnumerable) for basic functionality - Task intermediate = WhenAny((IEnumerable)tasks); - - // Return a continuation task with the correct result type - return intermediate.ContinueWith(Task.TaskWhenAnyCast.Value, default, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); - } + public static Task> WhenAny(IEnumerable> tasks) => + WhenAny>(tasks); #endregion internal static Task CreateUnwrapPromise(Task outerTask, bool lookForOce) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskFactory.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskFactory.cs index 15ed14a929c0b..5344ee17cc2cd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskFactory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskFactory.cs @@ -2261,15 +2261,15 @@ public Task ContinueWhenAll(Task, ITaskCompletionAction + internal sealed class CompleteOnInvokePromise : Task, ITaskCompletionAction where TTask : Task { private const int CompletedFlag = 0b_01; private const int SyncBlockingFlag = 0b_10; - private IList? _tasks; // must track this for cleanup + private IList? _tasks; // must track this for cleanup private int _stateFlags; - public CompleteOnInvokePromise(IList tasks, bool isSyncBlocking) + public CompleteOnInvokePromise(IList tasks, bool isSyncBlocking) { Debug.Assert(tasks != null, "Expected non-null collection of tasks"); _tasks = tasks; @@ -2305,7 +2305,7 @@ public void Invoke(Task completingTask) if (s_asyncDebuggingEnabled) RemoveFromActiveTasks(this); - bool success = TrySetResult(completingTask); + bool success = TrySetResult((TTask)completingTask); Debug.Assert(success, "Only one task should have gotten to this point, and thus this must be successful."); // We need to remove continuations that may be left straggling on other tasks. @@ -2313,12 +2313,12 @@ public void Invoke(Task completingTask) // This may also help to avoided unnecessary invocations of this whenComplete delegate. // Note that we may be attempting to remove a continuation from a task that hasn't had it // added yet; while there's overhead there, the operation won't hurt anything. - IList? tasks = _tasks; + IList? tasks = _tasks; Debug.Assert(tasks != null, "Should not have been nulled out yet."); int numTasks = tasks.Count; for (int i = 0; i < numTasks; i++) { - Task task = tasks[i]; + TTask task = tasks[i]; if (task != null && // if an element was erroneously nulled out concurrently, just skip it; worst case is we don't remove a continuation !task.IsCompleted) task.RemoveContinuation(this); } @@ -2333,13 +2333,12 @@ public void Invoke(Task completingTask) // we don't need to be concerned about concurrent modifications to the list. If the task list // is an array, it should be a defensive copy if this functionality is being used // asynchronously (e.g. WhenAny) rather than synchronously (e.g. WaitAny). - internal static Task CommonCWAnyLogic(IList tasks, bool isSyncBlocking = false) + internal static Task CommonCWAnyLogic(IList tasks, bool isSyncBlocking = false) where TTask : Task { Debug.Assert(tasks != null); // Create a promise task to be returned to the user. - // (If this logic ever changes, also update CommonCWAnyLogicCleanup.) - var promise = new CompleteOnInvokePromise(tasks, isSyncBlocking); + var promise = new CompleteOnInvokePromise(tasks, isSyncBlocking); // At the completion of any of the tasks, complete the promise. @@ -2395,7 +2394,7 @@ internal static void CommonCWAnyLogicCleanup(Task continuation) { // Force cleanup of the promise (e.g. removing continuations from each // constituent task), by completing the promise with any value (it's not observable). - ((CompleteOnInvokePromise)continuation).Invoke(null!); + ((CompleteOnInvokePromise)continuation).Invoke(null!); } ///