diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 70bbaae06fe..6e60de2a2e3 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -2,3 +2,7 @@ * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) + +### Added + +* Add `Async.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes ValueTask support, and a SRTP-based overload accepting any task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785)) diff --git a/src/FSharp.Core/async.fs b/src/FSharp.Core/async.fs index f18e451f357..58f78a9891b 100644 --- a/src/FSharp.Core/async.fs +++ b/src/FSharp.Core/async.fs @@ -13,6 +13,7 @@ open System.Runtime.ExceptionServices open System.Threading open System.Threading.Tasks open Microsoft.FSharp.Core +open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators open Microsoft.FSharp.Control open Microsoft.FSharp.Collections @@ -1210,16 +1211,30 @@ module AsyncPrimitives = task + // Used by Async.Await path to elide egregious AggregateException wrapping + [] + let UnwrapExn (exn: AggregateException) = + if exn.InnerExceptions.Count = 1 then + exn.InnerExceptions[0] + else + exn + // Call the appropriate continuation on completion of a task [] - let OnTaskCompleted (completedTask: Task<'T>) (ctxt: AsyncActivation<'T>) = + let OnTaskCompleted unwrap (completedTask: Task<'T>) (ctxt: AsyncActivation<'T>) = assert completedTask.IsCompleted if completedTask.IsCanceled then let edi = ExceptionDispatchInfo.Capture(TaskCanceledException completedTask) ctxt.econt edi elif completedTask.IsFaulted then - let edi = ExceptionDispatchInfo.RestoreOrCapture completedTask.Exception + let e = + if unwrap then + UnwrapExn completedTask.Exception + else + completedTask.Exception + + let edi = ExceptionDispatchInfo.RestoreOrCapture e ctxt.econt edi else ctxt.cont completedTask.Result @@ -1229,14 +1244,20 @@ module AsyncPrimitives = // the overall async (they may be governed by different cancellation tokens, or // the task may not have a cancellation token at all). [] - let OnUnitTaskCompleted (completedTask: Task) (ctxt: AsyncActivation) = + let OnUnitTaskCompleted unwrap (completedTask: Task) (ctxt: AsyncActivation) = assert completedTask.IsCompleted if completedTask.IsCanceled then let edi = ExceptionDispatchInfo.Capture(TaskCanceledException(completedTask)) ctxt.econt edi elif completedTask.IsFaulted then - let edi = ExceptionDispatchInfo.RestoreOrCapture completedTask.Exception + let e = + if unwrap then + UnwrapExn completedTask.Exception + else + completedTask.Exception + + let edi = ExceptionDispatchInfo.RestoreOrCapture e ctxt.econt edi else ctxt.cont () @@ -1246,10 +1267,10 @@ module AsyncPrimitives = // completing the task. This will install a new trampoline on that thread and continue the // execution of the async there. [] - let AttachContinuationToTask (task: Task<'T>) (ctxt: AsyncActivation<'T>) = + let AttachContinuationToTask unwrap (task: Task<'T>) (ctxt: AsyncActivation<'T>) = task.ContinueWith( Action>(fun completedTask -> - ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnTaskCompleted completedTask ctxt) + ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnTaskCompleted unwrap completedTask ctxt) |> unfake), TaskContinuationOptions.ExecuteSynchronously ) @@ -1261,16 +1282,36 @@ module AsyncPrimitives = // completing the task. This will install a new trampoline on that thread and continue the // execution of the async there. [] - let AttachContinuationToUnitTask (task: Task) (ctxt: AsyncActivation) = + let AttachContinuationToUnitTask unwrap (task: Task) (ctxt: AsyncActivation) = task.ContinueWith( Action(fun completedTask -> - ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnUnitTaskCompleted completedTask ctxt) + ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnUnitTaskCompleted unwrap completedTask ctxt) |> unfake), TaskContinuationOptions.ExecuteSynchronously ) |> ignore |> fake + let AwaitTask unwrap (task: Task<'T>) = + MakeAsyncWithCancelCheck(fun ctxt -> + if task.IsCompleted then + // Run synchronously without installing new trampoline + OnTaskCompleted unwrap task ctxt + else + // Continue asynchronously, via syncContext if necessary, installing new trampoline + let ctxt = DelimitSyncContext ctxt + ctxt.ProtectCode(fun () -> AttachContinuationToTask unwrap task ctxt)) + + let AwaitUnitTask unwrap (task: Task) = + MakeAsyncWithCancelCheck(fun ctxt -> + if task.IsCompleted then + // Continue synchronously without installing new trampoline + OnUnitTaskCompleted unwrap task ctxt + else + // Continue asynchronously, via syncContext if necessary, installing new trampoline + let ctxt = DelimitSyncContext ctxt + ctxt.ProtectCode(fun () -> AttachContinuationToUnitTask unwrap task ctxt)) + /// Removes a registration places on a cancellation token let DisposeCancellationRegistration (registration: byref) = match registration with @@ -2203,24 +2244,58 @@ type Async = CreateWhenCancelledAsync compensation computation static member AwaitTask(task: Task<'T>) : Async<'T> = - MakeAsyncWithCancelCheck(fun ctxt -> - if task.IsCompleted then - // Run synchronously without installing new trampoline - OnTaskCompleted task ctxt - else - // Continue asynchronously, via syncContext if necessary, installing new trampoline - let ctxt = DelimitSyncContext ctxt - ctxt.ProtectCode(fun () -> AttachContinuationToTask task ctxt)) + AwaitTask false task static member AwaitTask(task: Task) : Async = - MakeAsyncWithCancelCheck(fun ctxt -> - if task.IsCompleted then - // Continue synchronously without installing new trampoline - OnUnitTaskCompleted task ctxt - else - // Continue asynchronously, via syncContext if necessary, installing new trampoline - let ctxt = DelimitSyncContext ctxt - ctxt.ProtectCode(fun () -> AttachContinuationToUnitTask task ctxt)) + AwaitUnitTask false task + + static member Await(task: Task<'T>) : Async<'T> = + AwaitTask true task + + static member Await(task: Task) : Async = + AwaitUnitTask true task + +#if NETSTANDARD2_1 + static member Await(task: ValueTask<'T>) : Async<'T> = + if task.IsCompletedSuccessfully then + CreateReturnAsync(task.GetAwaiter().GetResult()) + else + AwaitTask true (task.AsTask()) + + static member Await(task: ValueTask) : Async = + if task.IsCompletedSuccessfully then + CreateReturnAsync(task.GetAwaiter().GetResult()) + else + AwaitUnitTask true (task.AsTask()) +#endif + +module AsyncTaskLikeExtensions = + + type Async with + + [] + static member inline Await< ^TaskLike, ^Awaiter, 'T + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'T)> + (task: ^TaskLike) + : Async<'T> = + Async.FromContinuations(fun (cont, econt, _ccont) -> + let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) task) + + if (^Awaiter: (member get_IsCompleted: unit -> bool) awaiter) then + try + cont ((^Awaiter: (member GetResult: unit -> 'T) awaiter)) + with e -> + econt e + else + (awaiter :> ICriticalNotifyCompletion) + .UnsafeOnCompleted(fun () -> + try + cont ((^Awaiter: (member GetResult: unit -> 'T) awaiter)) + with e -> + econt e)) module CommonExtensions = diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi index b2fe66ddd13..f9cc5b21442 100644 --- a/src/FSharp.Core/async.fsi +++ b/src/FSharp.Core/async.fsi @@ -5,9 +5,11 @@ namespace Microsoft.FSharp.Control open System open System.Threading open System.Threading.Tasks + open System.Runtime.CompilerServices open System.Runtime.ExceptionServices open Microsoft.FSharp.Core + open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Control open Microsoft.FSharp.Collections @@ -740,47 +742,186 @@ namespace Microsoft.FSharp.Control /// static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout:int -> Async - /// Return an asynchronous computation that will wait for the given task to complete and return - /// its result. - /// + /// Creates an asynchronous computation that will wait asynchronously for the given task to complete, returning + /// its result. Note exceptions are wrapped in ; for new + /// code, prefer Async.Await, which surfaces single exceptions directly. /// The task to await. - /// - /// If an exception occurs in the asynchronous computation then an exception is re-raised by this - /// function. - /// - /// If the task is cancelled then is raised. Note + /// If the task is canceled then is raised. Note /// that the task may be governed by a different cancellation token to the overall async computation /// where the AwaitTask occurs. In practice you should normally start the task with the /// cancellation token returned by let! ct = Async.CancellationToken, and catch - /// any at the point where the + /// any at the point where the /// overall async is started. /// - /// /// Awaiting Results - /// - /// + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test"; 42) + /// async { + /// try + /// let! _ = Async.AwaitTask t + /// () + /// with + /// | :? System.InvalidOperationException -> + /// printfn "unreachable" // will not match: exception is wrapped in AggregateException + /// | :? System.AggregateException as e -> + /// printfn $"Caught: {e.InnerException.Message}" + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The InvalidOperationException branch is not reached because + /// exceptions from tasks are always wrapped in . Contrast with Async.Await. + /// static member AwaitTask: task: Task<'T> -> Async<'T> - /// Return an asynchronous computation that will wait for the given task to complete and return - /// its result. - /// + /// Creates an asynchronous computation that will wait asynchronously for the given task to complete. + /// Note exceptions are wrapped in ; for new + /// code, prefer Async.Await, which surfaces single exceptions directly. /// The task to await. - /// - /// If an exception occurs in the asynchronous computation then an exception is re-raised by this - /// function. - /// - /// If the task is cancelled then is raised. Note + /// If the task is canceled then is raised. Note /// that the task may be governed by a different cancellation token to the overall async computation /// where the AwaitTask occurs. In practice you should normally start the task with the /// cancellation token returned by let! ct = Async.CancellationToken, and catch - /// any at the point where the + /// any at the point where the /// overall async is started. /// + /// Awaiting Results + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test") + /// async { + /// try + /// do! Async.AwaitTask t + /// with + /// | :? System.InvalidOperationException -> + /// printfn "unreachable" // will not match: exception is wrapped in AggregateException + /// | :? System.AggregateException as e -> + /// printfn $"Caught: {e.InnerException.Message}" + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The InvalidOperationException branch is not reached because + /// exceptions from tasks are always wrapped in . Contrast with Async.Await. + /// + static member AwaitTask: task: Task -> Async + + /// Creates an asynchronous computation that will wait for the given task to complete and return + /// its result. + /// + /// The task to await. + /// + /// Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask. + /// + /// If the task is canceled then is raised. + /// /// /// Awaiting Results /// - /// - static member AwaitTask: task: Task -> Async + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test"; 42) + /// async { + /// try + /// let! _ = Async.Await t + /// () + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The AggregateException branch is not reached because a + /// single-inner exception is unwrapped. Contrast with Async.AwaitTask. + /// + static member Await: task: Task<'T> -> Async<'T> + + /// Creates an asynchronous computation that will wait for the given task to complete. + /// The task to await. + /// Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask. + /// + /// If the task is canceled then is raised. + /// + /// Awaiting Results + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test") + /// async { + /// try + /// do! Async.Await t + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The AggregateException branch is not reached because a + /// single-inner exception is unwrapped. Contrast with Async.AwaitTask. + /// + static member Await: task: Task -> Async + +#if NETSTANDARD2_1 + /// Creates an asynchronous computation that will wait for the given ValueTask to complete and return + /// its result. + /// The ValueTask to await. + /// Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask. + /// + /// If the task is canceled then is raised. + /// + /// Awaiting Results + /// + /// + /// let vt = ValueTask<int>(Task.Run(fun () -> invalidOp "test"; 42)) + /// async { + /// try + /// let! _ = Async.Await vt + /// () + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. + /// + static member Await: task: ValueTask<'T> -> Async<'T> + + /// Creates an asynchronous computation that will wait for the given ValueTask to complete. + /// The ValueTask to await. + /// Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask. + /// + /// If the task is canceled then is raised. + /// + /// Awaiting Results + /// + /// + /// let vt = ValueTask(Task.Run(fun () -> invalidOp "test")) + /// async { + /// try + /// do! Async.Await vt + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. + /// + static member Await: task: ValueTask -> Async +#endif /// /// Creates an asynchronous computation that will sleep for the given time. This is scheduled @@ -1074,6 +1215,50 @@ namespace Microsoft.FSharp.Control computation:Async<'T> * ?cancellationToken:CancellationToken-> Task<'T> + /// A module of extension members providing support for awaiting any task-like value via the GetAwaiter pattern. + /// + /// Awaiting Results + [] + module AsyncTaskLikeExtensions = + + type Async with + + /// Creates an asynchronous computation that will wait for the given task-like value to complete and return + /// its result. + /// The task-like value to await. + /// The value must satisfy the GetAwaiter pattern: it must have a GetAwaiter() method + /// returning an awaiter implementing + /// with IsCompleted and GetResult() members. Exceptions thrown by GetResult() are + /// propagated directly. + /// + /// This overload uses statically resolved type parameters (SRTP) so it can accept any task-like type. + /// The specific overloads for , , + /// and + /// are preferred when the argument type is known. + /// + /// Awaiting Results + /// + /// + /// // A minimal custom task-like type + /// type MyTask<'T>(task: System.Threading.Tasks.Task<'T>) = + /// member _.GetAwaiter() = task.GetAwaiter() + /// + /// let myTask = MyTask(System.Threading.Tasks.Task.FromResult 42) + /// async { + /// let! result = Async.Await myTask + /// printfn $"Result: {result}" + /// } |> Async.RunSynchronously + /// + /// Prints Result: 42. + /// + [] + static member inline Await< ^TaskLike, ^Awaiter, 'T> : + task: ^TaskLike -> Async<'T> + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'T) + /// The F# compiler emits references to this type to implement F# async expressions. /// /// Async Internals diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl index 5b6cc0bce4e..a1816af6912 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl @@ -623,6 +623,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -642,6 +644,7 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -659,6 +662,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl index 217d4b7c837..5e1ad684f76 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -623,6 +623,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -642,6 +644,7 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -659,6 +662,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl index 43defdb622e..b082efcca7b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl @@ -625,6 +625,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -644,6 +646,8 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.ValueTask) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -661,6 +665,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.ValueTask`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl index ed913ea04d3..80d9c28153e 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -625,6 +625,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -644,6 +646,8 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.ValueTask) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -661,6 +665,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.ValueTask`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs index 571a4250175..defc4a3c3ff 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs @@ -220,14 +220,14 @@ type AsyncType() = | _ -> reraise() Assert.True (tcs.Task.IsCompleted, "Task is not completed") - [] - member _.RunSynchronouslyCancellationWithDelayedResult () = + [] + member _.RunSynchronouslyCancellationWithDelayedResult(newAwait: bool) = let cts = new CancellationTokenSource() let tcs = TaskCompletionSource() let _ = cts.Token.Register(fun () -> tcs.SetResult 42) let a = async { - cts.CancelAfter (100) - let! result = tcs.Task |> Async.AwaitTask + cts.CancelAfter(100) + let! result = tcs.Task |> if newAwait then Async.Await else Async.AwaitTask return result } let cancelled = @@ -367,127 +367,127 @@ type AsyncType() = Assert.True(t.IsCanceled) Assert.True(cancelled) - [] - member _.TaskAsyncValue () = + [] + member _.TaskAsyncValue(newAwait: bool) = let s = "Test" use t = Task.Factory.StartNew(Func<_>(fun () -> s)) let a = async { - let! s1 = Async.AwaitTask(t) - return s = s1 - } - Async.RunSynchronously(a) |> Assert.True + let! s1 = t |> if newAwait then Async.Await else Async.AwaitTask + return s = s1 + } + let ok = Async.RunSynchronously a + Assert.True ok - [] - member _.AwaitTaskCancellation () = - let test() = async { - let tcs = new System.Threading.Tasks.TaskCompletionSource() + [] + member _.AwaitTaskCancellation(newAwait: bool) = + let a = async { + let tcs = System.Threading.Tasks.TaskCompletionSource() tcs.SetCanceled() try - do! Async.AwaitTask tcs.Task + do! tcs.Task |> if newAwait then Async.Await else Async.AwaitTask return false - with :? System.OperationCanceledException -> return true + with :? OperationCanceledException -> return true } - - Async.RunSynchronously(test()) |> Assert.True + let ok = Async.RunSynchronously a + Assert.True ok [] member _.AwaitCompletedTask() = - let test() = async { + let a = async { let threadIdBefore = Thread.CurrentThread.ManagedThreadId do! Async.AwaitTask Task.CompletedTask let threadIdAfter = Thread.CurrentThread.ManagedThreadId return threadIdBefore = threadIdAfter } + let ok = Async.RunSynchronously a + Assert.True ok - Async.RunSynchronously(test()) |> Assert.True - - [] - member _.AwaitTaskCancellationUntyped () = - let test() = async { - let tcs = new System.Threading.Tasks.TaskCompletionSource() + [] + member _.AwaitTaskCancellationUntyped(newAwait: bool) = + let a = async { + let tcs = System.Threading.Tasks.TaskCompletionSource() tcs.SetCanceled() try - do! Async.AwaitTask (tcs.Task :> Task) + do! tcs.Task :> Task |> if newAwait then Async.Await else Async.AwaitTask return false - with :? System.OperationCanceledException -> return true + with :? OperationCanceledException -> return true } + let ok = Async.RunSynchronously a + Assert.True ok - Async.RunSynchronously(test()) |> Assert.True - - [] - member _.TaskAsyncValueException () = + [] + member _.TaskAsyncValueException(newAwait: bool) = use t = Task.Factory.StartNew(Func(fun () -> raise <| Exception())) let a = async { - try - let! v = Async.AwaitTask(t) - return false - with e -> return true - } - Async.RunSynchronously(a) |> Assert.True + try let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return false + with e -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok - [] - member _.TaskAsyncValueCancellation () = + [] + member _.TaskAsyncValueCancellation(newAwait: bool) = use ewh = new ManualResetEvent(false) let cts = new CancellationTokenSource() let token = cts.Token use t : Task = Task.Factory.StartNew(Func(fun () -> while not token.IsCancellationRequested do ()), token) let cancelled = ref true - let a = - async { - try - use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) - let! v = Async.AwaitTask(t) - return v - // AwaitTask raises TaskCanceledException when it is canceled, it is a valid result of this test - with - :? TaskCanceledException -> - ewh.Set() |> ignore // this is ok - } + let a = async { + try + use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) + let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return v + // A canceled task yields TaskCanceledException via the exception continuation + with + :? TaskCanceledException -> + ewh.Set() |> ignore // this is ok + } let t1 = Async.StartAsTask a cts.Cancel() ewh.WaitOne(10000) |> ignore // Don't leave unobserved background tasks, because they can crash the test run. t1.Wait() - [] - member _.NonGenericTaskAsyncValue () = + [] + member _.NonGenericTaskAsyncValue(newAwait: bool) = let mutable hasBeenCalled = false use t = Task.Factory.StartNew(Action(fun () -> hasBeenCalled <- true)) let a = async { - do! Async.AwaitTask(t) - return true - } - let result = Async.RunSynchronously(a) - (hasBeenCalled && result) |> Assert.True + do! t |> if newAwait then Async.Await else Async.AwaitTask + return true + } + let ok = Async.RunSynchronously a + Assert.True(hasBeenCalled && ok) - [] - member _.NonGenericTaskAsyncValueException () = + [] + member _.NonGenericTaskAsyncValueException(newAwait: bool) = use t = Task.Factory.StartNew(Action(fun () -> raise <| Exception())) let a = async { - try - let! v = Async.AwaitTask(t) - return false - with e -> return true - } - Async.RunSynchronously(a) |> Assert.True + try + let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return false + with e -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok - [] - member _.NonGenericTaskAsyncValueCancellation () = + [] + member _.NonGenericTaskAsyncValueCancellation(newAwait: bool) = use ewh = new ManualResetEvent(false) let cts = new CancellationTokenSource() let token = cts.Token use t = Task.Factory.StartNew(Action(fun () -> while not token.IsCancellationRequested do ()), token) - let a = - async { - try - use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) - let! v = Async.AwaitTask(t) - return v - // AwaitTask raises TaskCanceledException when it is canceled, it is a valid result of this test - with - :? TaskCanceledException -> - ewh.Set() |> ignore // this is ok - } + let a = async { + try + use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) + let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return v + // A canceled task yields TaskCanceledException via the exception continuation + with + :? TaskCanceledException -> + ewh.Set() |> ignore // this is ok + } let t1 = Async.StartAsTask a cts.Cancel() ewh.WaitOne(10000) |> ignore @@ -510,19 +510,358 @@ type AsyncType() = ewh.Wait(10000) |> ignore Assert.False hasThrown - [] - member _.NoStackOverflowOnRecursion() = - + [] + member _.NoStackOverflowOnRecursion(newAwait: bool) = let mutable hasThrown = false let rec loop (x: int) = async { - do! Task.CompletedTask |> Async.AwaitTask + do! Task.CompletedTask |> if newAwait then Async.Await else Async.AwaitTask Console.WriteLine (if x = 10000 then failwith "finish" else x) return! loop(x+1) } - try - Async.RunSynchronously (loop 0) - hasThrown <- false + try Async.RunSynchronously (loop 0) + hasThrown <- false with Failure "finish" -> hasThrown <- true Assert.True hasThrown + + // Both AwaitTask and Await ignore the ambient cancellation token while waiting + // (Same goes for the typed variants) + [] + member _.``Both AwaitTask and Await ignore ambient cancellation while waiting``(newAwait) = + let cts = new CancellationTokenSource() + let tcs = TaskCompletionSource() // task that never completes + let res = TaskCompletionSource() + + let a = async { + try do! tcs.Task |> if newAwait then Async.Await else Async.AwaitTask + res.TrySetResult true |> ignore + with _ -> res.TrySetResult false |> ignore + } + + Async.Start(a, cts.Token) + // NOTE we only cancel during the Await/AwaitTask - the initial check would throw if we canceled before the Start() + cts.CancelAfter 100 + + // AwaitTask should NOT honor the ambient CT trigger + let taskCompleted = res.Task.Wait 500 + Assert.False(taskCompleted, "Await/AwaitTask should not have responded to ambient CT cancellation") + tcs.TrySetResult() |> ignore // clean up + res.Task.Wait() + + (* When an AggregateException has multiple inner exceptions, Await and AwaitTask behave identically *) + + [] + member _.``Await and AwaitTask(Task<'T>) valid AggregateException is surfaced``(newAwait) = + let tcs = TaskCompletionSource() + tcs.SetException [ ArgumentException "a" :> exn; InvalidOperationException "b" :> exn ] + let a = async { + try + let! _ = tcs.Task |> if newAwait then Async.Await else Async.AwaitTask + return false + with :? AggregateException as ae -> return ae.InnerExceptions.Count = 2 + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await and AwaitTask(Task) valid AggregateException is surfaced``(newAwait) = + let tcs = TaskCompletionSource() + tcs.SetException [| ArgumentException "a" :> exn; InvalidOperationException "b" |] + let a = async { + try + do! tcs.Task |> if newAwait then Async.Await else Async.AwaitTask + return false + with :? AggregateException as ae -> return ae.InnerExceptions.Count = 2 + } + let ok = Async.RunSynchronously a + Assert.True ok + + (* Async.Await behavioral differences + + The following tests demonstrate where Async.Await deliberately differs from Async.AwaitTask *) + + // Async.AwaitTask(Task) surfaces the wrapping AggregateException ... + [] + member _.``AwaitTask(Task) egregious AggregateException is unchanged``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try do! Async.AwaitTask tcs.Task + return false + with :? AggregateException -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok + + // ... whereas Async.Await(Task) surfaces the inner exception directly. + [] + member _.``Await(Task) egregious AggregateException is unwrapped``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try do! Async.Await tcs.Task + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok + + // Async.AwaitTask(Task<'T>) surfaces the wrapping AggregateException ... + [] + member _.``AwaitTask(Task<'T>) egregious AggregateException is unchanged``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.AwaitTask tcs.Task + return false + with :? AggregateException -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok + + // ... whereas Async.Await(Task<'T>) surfaces the inner exception directly. + [] + member _.``Await(Task<'T>) egregious AggregateException is unwrapped``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.Await tcs.Task + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok + + (* Await(Task/Task<'T>) overloads happy path *) + + [] + member _.``Await(Task<'T>) happy path``() = + let a = async { + let! v = Async.Await(System.Threading.Tasks.Task.FromResult(42)) + return v = 42 + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(Task) happy path``() = + let a = async { + do! Async.Await(System.Threading.Tasks.Task.CompletedTask) + return true + } + let ok = Async.RunSynchronously a + Assert.True ok + +#if NETSTANDARD2_1 + (* Await(ValueTask and ValueTask<'T>) overloads coverage of mainline behaviors *) + + [] + member _.``Await(ValueTask) happy path``() = + let a = async { + do! Async.Await(ValueTask()) + return true + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(ValueTask<'T>) happy path``() = + let a = async { + let! v = Async.Await(ValueTask(42)) + return v = 42 + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(ValueTask) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let task = ValueTask(tcs.Task :> Task) + let a = async { + try do! Async.Await task + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(ValueTask<'T>) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.Await(ValueTask(tcs.Task)) + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok +#endif + +[] +module AsyncTaskLikeAwaitTests = + + // Minimal custom task-like type wrapping Task<'T> + type MyTask<'T>(inner: Task<'T>) = + member _.GetAwaiter() = inner.GetAwaiter() + + // Minimal custom unit-returning task-like + type MyUnitTask(inner: Task) = + member _.GetAwaiter() = inner.GetAwaiter() + + [] + let ``Await(task-like) happy path with result``() = + let result = + async { + let! v = Async.Await(MyTask(Task.FromResult 99)) + return v + } + |> Async.RunSynchronously + Assert.Equal(99, result) + + [] + let ``Await(task-like) happy path unit``() = + async { + do! Async.Await(MyUnitTask(Task.CompletedTask)) + } + |> Async.RunSynchronously + + [] + let ``Await(task-like) deferred completion``() = + let tcs = TaskCompletionSource() + let t = + async { + let! v = Async.Await(MyTask(tcs.Task)) + return v + } + |> Async.StartAsTask + Assert.False(t.IsCompleted, "Should not be done before TCS is set") + tcs.SetResult 7 + t.Wait(TimeSpan.FromSeconds 5.0) |> ignore + Assert.Equal(7, t.Result) + + [] + let ``Await(task-like) exception propagation``() = + let tcs = TaskCompletionSource() + let a = + async { + try let! _ = Async.Await(MyTask(tcs.Task)) + return false + with :? InvalidOperationException as e -> + return e.Message = "boom" + } + tcs.SetException(InvalidOperationException "boom") + let ok = Async.RunSynchronously a + Assert.True ok + + [] + let ``Await(YieldAwaitable) yields and resumes``() = + // Task.Yield() returns a YieldAwaitable which is a struct — exercises the struct-awaiter path. + let mutable before, after = false, false + async { + before <- true + do! Async.Await(Task.Yield()) + after <- true + } + |> Async.RunSynchronously + Assert.True(before && after) + + [] + let ``Await(ConfiguredTaskAwaitable) from ConfigureAwait``() = + // task.ConfigureAwait(false) returns a ConfiguredTaskAwaitable — a common real-world task-like. + let result = + async { + let! v = Async.Await(Task.FromResult(42).ConfigureAwait(false)) + return v + } + |> Async.RunSynchronously + Assert.Equal(42, result) + +[] +module AsyncAwaitStackTraceTests = + + open System.Runtime.CompilerServices + + // Minimal wrapper to route through the SRTP overload instead of the specific Task<'T> overload. + // Task<'T>, Task, ValueTask<'T>, and ValueTask all have higher-priority intrinsic overloads. + type TaskWrapper<'T>(inner: Task<'T>) = + member _.GetAwaiter() = inner.GetAwaiter() + + // Plain function — provides a stable named frame at the outermost throw site. + [] + let throwAtLevel1 () : unit = invalidOp "boom" + + // Level-1 task: thin wrapper around the direct throw. + [] + let level1Task () : Task = task { throwAtLevel1 () } + + // Level-2 task: introduces a real async await boundary between levels 1 and 2. + [] + let level2Task () : Task = task { do! level1Task () } + + // Run via StartImmediateAsTask + .Wait() and return the inner exception. + // Using StartImmediateAsTask (not RunSynchronously) ensures that the async-layer + // exception machinery goes through TaskCompletionSource.SetException, which preserves + // the stack trace rather than rethrowing synchronously and potentially truncating it. + let runAndCaptureException (computation: Async) : exn = + // TODO swap in usage of Async.RunSynchronouslyImmediate + let t = Async.StartImmediateAsTask computation + let ae = Assert.Throws(fun () -> t.Wait()) + ae.InnerException + + // Template assertion: levels 1 and 2 must be traceable in the stack trace + // regardless of which Async.Await overload is used. + let checkTrace totalCount (e: exn) = + let trace = e.StackTrace + // stacktrace should be relatively compact and not bloat the logs, so unconditionally print it to save time analyzing regressions + printfn "EDI trace ====" + printfn "%s" trace + printfn "==== EDI trace" + Assert.NotNull(trace) + Assert.Contains("throwAtLevel1", trace) + Assert.Contains("level1Task", trace) + Assert.Contains("level2Task", trace) +#if !NETFRAMEWORK // downlevel has interstitial layers we are not seeking to characterize at this point + Assert.Equal(totalCount, trace.Split('\n').Length) +#endif + + // --- Tests per overload --- + // The common skeleton is: build a 3-level chain (throwAtLevel1 → level1Task → level2Task), + // wrap the outermost level in an async block using Async.Await, run via + // StartImmediateAsTask + .Wait(), and assert on the resulting exception's stack trace. + + [] + let ``Await Task-of-T: all three levels visible in stack trace`` () = + let e = runAndCaptureException (async { do! Async.Await(level2Task()) }) + checkTrace 3 e + + [] + let ``Await Task (non-generic): all three levels visible in stack trace`` () = + let e = runAndCaptureException (async { do! Async.Await(level2Task() :> Task) }) + checkTrace 3 e + // Same behavior as the Task<'T> overload — see comment there. + +#if NETSTANDARD2_1 + [] + let ``Await ValueTask-of-T: all three levels visible in stack trace`` () = + // For a faulted ValueTask, IsCompletedSuccessfully is false; the overload falls + // through to AwaitTask, which takes the same path as the specific Task<'T> overload. + let e = runAndCaptureException (async { do! Async.Await(ValueTask(level2Task())) }) + checkTrace 3 e + + [] + let ``Await ValueTask (non-generic): all three levels visible in stack trace`` () = + // Same as ValueTask<'T>: falls through to AwaitUnitTask for the non-successfully-completed case. + let e = runAndCaptureException (async { do! Async.Await(ValueTask(level2Task() :> Task)) }) + + checkTrace 3 e +#endif + + [] + let ``Await task-like via SRTP overload: all three levels visible in stack trace`` () = + let e = runAndCaptureException (async { do! Async.Await(TaskWrapper(level2Task())) }) + + // 4 instead of 3 as current impl has an outer "at FSharp.Core.UnitTests.Control.AsyncAwaitStackTraceTests.e@836-9.Invoke(Tuple`3 tupledArg) + checkTrace 4 e \ No newline at end of file diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs index bb9571afa67..33279cfab1f 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs @@ -268,6 +268,7 @@ type UsingMSBuild() = let expectedTooltip = """ type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * objnull -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) + static member Await: task: Task<'T> -> Async<'T> + 3 overloads static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate and 'Del: not null) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload @@ -276,10 +277,13 @@ type Async = static member Catch: computation: Async<'T> -> Async> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * objnull -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads - static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ... Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") - + // Hack to deal with fact that FSharp.Core's Async.Await will have 2 overloads in netstandard2.0 but 4 in netstandard2.1 + let checkTooltip expected ((tooltip: string, span : TextSpan), (row, col)) = + let tooltip = tooltip.Replace("static member Await: task: Task<'T> -> Async<'T> + 1 overload", + "static member Await: task: Task<'T> -> Async<'T> + 3 overloads") + checkTooltip expected ((tooltip, span), (row, col)) this.CheckTooltip(source, "Asyn", false, checkTooltip expectedTooltip) []