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 a7c027f3984297..a8b04c6f087455 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 @@ -5952,7 +5952,6 @@ public static Task WhenAny(params Task[] tasks) return TaskFactory.CommonCWAnyLogic(tasksCopy); } - // TODO https://github.com/dotnet/runtime/issues/23021: Make this public. /// 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. @@ -5964,7 +5963,7 @@ public static Task WhenAny(params Task[] tasks) /// /// The or argument was null. /// - internal static Task WhenAny(Task task1, Task task2) => + public static Task WhenAny(Task task1, Task task2) => (task1 is null) || (task2 is null) ? throw new ArgumentNullException(task1 is null ? nameof(task1) : nameof(task2)) : task1.IsCompleted ? FromResult(task1) : task2.IsCompleted ? FromResult(task2) : @@ -6121,7 +6120,6 @@ public static Task> WhenAny(params Task[] tasks) TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); } - // TODO https://github.com/dotnet/runtime/issues/23021: Make this public. /// 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. @@ -6133,7 +6131,7 @@ public static Task> WhenAny(params Task[] tasks) /// /// The or argument was null. /// - internal static Task> WhenAny(Task task1, Task task2) => + public static Task> WhenAny(Task task1, Task task2) => (task1 is null) || (task2 is null) ? throw new ArgumentNullException(task1 is null ? nameof(task1) : nameof(task2)) : task1.IsCompleted ? FromResult(task1) : task2.IsCompleted ? FromResult(task2) : diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f64f478b022ed5..f6d1b38da719c0 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10702,8 +10702,10 @@ public static void WaitAll(System.Threading.Tasks.Task[] tasks, System.Threading public static System.Threading.Tasks.Task WhenAll(System.Collections.Generic.IEnumerable> tasks) { throw null; } public static System.Threading.Tasks.Task WhenAll(params System.Threading.Tasks.Task[] tasks) { throw null; } public static System.Threading.Tasks.Task WhenAny(System.Collections.Generic.IEnumerable tasks) { throw null; } + public static System.Threading.Tasks.Task WhenAny(System.Threading.Tasks.Task task1, System.Threading.Tasks.Task task2) { throw null; } public static System.Threading.Tasks.Task WhenAny(params System.Threading.Tasks.Task[] tasks) { throw null; } public static System.Threading.Tasks.Task> WhenAny(System.Collections.Generic.IEnumerable> tasks) { throw null; } + public static System.Threading.Tasks.Task> WhenAny(System.Threading.Tasks.Task task1, System.Threading.Tasks.Task task2) { throw null; } public static System.Threading.Tasks.Task> WhenAny(params System.Threading.Tasks.Task[] tasks) { throw null; } public static System.Runtime.CompilerServices.YieldAwaitable Yield() { throw null; } } diff --git a/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs b/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs index 4cb311fb498680..efaedfdaf4d589 100644 --- a/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs +++ b/src/libraries/System.Threading.Tasks/tests/MethodCoverage.cs @@ -179,6 +179,92 @@ public static void TaskWaitAny_WhenAny() Assert.True(whenAnyVerification, string.Format("The id for whenAny is not correct expected to be {0} or {1} and it is {2}", t11.Id, t21.Id, taskId)); } + [Fact] + public static void Task_WhenAny_TwoTasks_InvalidArgs_Throws() + { + AssertExtensions.Throws("task1", () => Task.WhenAny(null, Task.CompletedTask)); + AssertExtensions.Throws("task2", () => Task.WhenAny(Task.CompletedTask, null)); + + AssertExtensions.Throws("task1", () => Task.WhenAny(null, Task.FromResult(1))); + AssertExtensions.Throws("task2", () => Task.WhenAny(Task.FromResult(2), null)); + } + + [Fact] + public static async Task Task_WhenAny_TwoTasks_OnePreCompleted() + { + Task t1 = Task.FromResult(1); + Task t2 = new TaskCompletionSource().Task; + + Assert.Same(t1, await Task.WhenAny((Task)t1, (Task)t2)); + Assert.Same(t1, await Task.WhenAny((Task)t2, (Task)t1)); + + Assert.Same(t1, await Task.WhenAny(t1, t2)); + Assert.Same(t1, await Task.WhenAny(t2, t1)); + } + + [Fact] + public static async Task Task_WhenAny_TwoTasks_BothPreCompleted() + { + Task t1 = Task.FromResult(1); + Task t2 = Task.FromResult(2); + + Assert.Same(t1, await Task.WhenAny((Task)t1, (Task)t2)); + Assert.Same(t1, await Task.WhenAny((Task)t1, (Task)t1)); + Assert.Same(t2, await Task.WhenAny((Task)t2, (Task)t1)); + + Assert.Same(t1, await Task.WhenAny(t1, t2)); + Assert.Same(t1, await Task.WhenAny(t1, t1)); + Assert.Same(t2, await Task.WhenAny(t2, t1)); + } + + [Fact] + public static async Task Task_WhenAny_TwoTasks_WakesOnFirstCompletion() + { + // Non-generic, first completes + { + var t1 = new TaskCompletionSource(); + var t2 = new TaskCompletionSource(); + + Task twa = Task.WhenAny((Task)t1.Task, (Task)t2.Task); + Assert.False(twa.IsCompleted); + t1.SetResult(42); + Assert.Same(t1.Task, await twa); + } + + // Generic, first completes + { + var t1 = new TaskCompletionSource(); + var t2 = new TaskCompletionSource(); + + Task> twa = Task.WhenAny(t1.Task, t2.Task); + Assert.False(twa.IsCompleted); + t1.SetResult(42); + Assert.Same(t1.Task, await twa); + } + + // Non-generic, second completes + { + var t1 = new TaskCompletionSource(); + var t2 = new TaskCompletionSource(); + + Task twa = Task.WhenAny((Task)t1.Task, (Task)t2.Task); + Assert.False(twa.IsCompleted); + t2.SetResult(42); + Assert.Same(t2.Task, await twa); + } + + // Generic, second completes + { + var t1 = new TaskCompletionSource(); + var t2 = new TaskCompletionSource(); + + Task> twa = Task.WhenAny(t1.Task, t2.Task); + Assert.False(twa.IsCompleted); + t2.SetResult(42); + Assert.Same(t2.Task, await twa); + } + } + [Fact] public static void CancellationTokenRegitration() {