diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 70bbaae06fe..047d557be84 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. ([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..2fce3bb6705 100644 --- a/src/FSharp.Core/async.fs +++ b/src/FSharp.Core/async.fs @@ -1210,16 +1210,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 +1243,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 +1266,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 +1281,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 +2243,30 @@ 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 CommonExtensions = diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi index b2fe66ddd13..848a09ed258 100644 --- a/src/FSharp.Core/async.fsi +++ b/src/FSharp.Core/async.fsi @@ -740,47 +740,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 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..2b534cc247d 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl @@ -642,6 +642,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 +660,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..d2682172d43 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -642,6 +642,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 +660,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..ab8571b1442 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl @@ -644,6 +644,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 +663,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..69fc090f3fc 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -644,6 +644,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 +663,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..9aea7f42a62 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,192 @@ 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 \ 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..04d4b0a9d68 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 @@ -279,7 +280,11 @@ type Async = 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, 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) []