diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 884146bc4e4..2cf66edaa10 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -8,6 +8,7 @@ ### Added * Add `Async.Await`, mirroring `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)) -* `Async.RunSynchronouslyImmediate`: runs work on the calling thread until the first asynchronous suspension (as opposed to `RunSynchronously`, which immediately offloads if not on a background and/or threadpool thread). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804)) -* Added modules for `Async`, `Task` and `ValueTask` with consistent `result`, `map`, `bind`, `ignore`, `catchWith`, `catch`, and `empty` functions ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844)) -* Added conversion functions `Task.ofValueTask` and `ValueTask.ofTask`. ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844)) +* Add `Async.StartTaskImmediate`: passes the ambient `Async.CancellationToken` to a task factory, then await the result using `Async.Await` semantics. Overloads for `Task`, `Task<'T>`, `ValueTask`, `ValueTask<'T>` and task-like `.GetAwaiter()` (via SRTP). ([Language Suggestion #1284](https://github.com/fsharp/fslang-suggestions/issues/1284), [PR #20258](https://github.com/dotnet/fsharp/pull/20258)) +* Add `Async.RunSynchronouslyImmediate`: runs work on the calling thread until the first asynchronous suspension (as opposed to `RunSynchronously`, which immediately offloads if not on a background and/or threadpool thread). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804)) +* Add modules for `Async`, `Task` and `ValueTask` with consistent `result`, `map`, `bind`, `ignore`, `catchWith`, `catch`, and `empty` functions ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844)) +* Add conversion functions `Task.ofValueTask` and `ValueTask.ofTask`. ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844)) diff --git a/src/FSharp.Core/async.fs b/src/FSharp.Core/async.fs index e73dc4aa230..bebe104e830 100644 --- a/src/FSharp.Core/async.fs +++ b/src/FSharp.Core/async.fs @@ -2268,6 +2268,20 @@ type Async = AwaitUnitTask true (task.AsTask()) #endif + static member StartTaskImmediate(createTask: CancellationToken -> Task<'T>) : Async<'T> = + CreateBindAsync Async.CancellationToken (createTask >> Async.Await) + + static member StartTaskImmediate(createTask: CancellationToken -> Task) : Async = + CreateBindAsync Async.CancellationToken (createTask >> Async.Await) + +#if NETSTANDARD2_1 + static member StartTaskImmediate(createTask: CancellationToken -> ValueTask<'T>) : Async<'T> = + CreateBindAsync Async.CancellationToken (createTask >> Async.Await) + + static member StartTaskImmediate(createTask: CancellationToken -> ValueTask) : Async = + CreateBindAsync Async.CancellationToken (createTask >> Async.Await) +#endif + module AsyncTaskLikeExtensions = type Async with @@ -2296,6 +2310,16 @@ module AsyncTaskLikeExtensions = with e -> econt e)) + [] + static member inline StartTaskImmediate< ^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)> + (createTask: CancellationToken -> ^TaskLike) + : Async<'T> = + CreateBindAsync Async.CancellationToken (createTask >> Async.Await) + module CommonExtensions = type System.IO.Stream with diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi index 2171f75164a..0f12de7a1b3 100644 --- a/src/FSharp.Core/async.fsi +++ b/src/FSharp.Core/async.fsi @@ -782,12 +782,16 @@ namespace Microsoft.FSharp.Control /// its result. Note exceptions are wrapped in ; for new /// code, prefer Async.Await, which surfaces single exceptions directly. /// The task to await. - /// If the task is canceled 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 - /// overall async is started. + /// overall async is started.

+ ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

///
/// Awaiting Results /// @@ -813,12 +817,15 @@ namespace Microsoft.FSharp.Control /// Note exceptions are wrapped in ; for new /// code, prefer Async.Await, which surfaces single exceptions directly. /// The task to await. - /// If the task is canceled 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 - /// overall async is started. + /// overall async is started.

+ ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

///
/// Awaiting Results /// @@ -856,6 +863,9 @@ namespace Microsoft.FSharp.Control /// typically tasks should be wired to the ambient cancellation token obtained via /// let! ct = Async.CancellationToken, catching /// where the overall async is started.

+ ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

///
/// /// Awaiting Results @@ -893,6 +903,9 @@ namespace Microsoft.FSharp.Control /// typically tasks should be wired to the ambient cancellation token obtained via /// let! ct = Async.CancellationToken, catching /// where the overall async is started.

+ ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

///
/// Awaiting Results /// @@ -929,6 +942,9 @@ namespace Microsoft.FSharp.Control /// typically tasks should be wired to the ambient cancellation token obtained via /// let! ct = Async.CancellationToken, catching /// where the overall async is started.

+ ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

/// /// Awaiting Results /// @@ -963,6 +979,9 @@ namespace Microsoft.FSharp.Control /// typically tasks should be wired to the ambient cancellation token obtained via /// let! ct = Async.CancellationToken, catching /// where the overall async is started.

+ ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

/// /// Awaiting Results /// @@ -983,6 +1002,79 @@ namespace Microsoft.FSharp.Control static member Await: task: ValueTask -> Async #endif + /// Creates an asynchronous computation that passes the ambient Async.CancellationToken to + /// createTask, and then awaits the resulting task, returning its result. + /// + /// A function that accepts a CancellationToken and returns a Task<'T>. + /// + /// The cancellation token of the enclosing async computation is automatically passed to + /// createTask, propagating cancellation naturally to the task without requiring manual token capture. + /// + /// The resulting task is awaited using ; + /// exception unwrapping and cancellation handling are as per that overload. + /// + /// Starting Async Computations + /// + /// + /// async { + /// let! text = Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync("file.txt", ct)) + /// printfn "Content: %s" text + /// } + /// + /// + static member StartTaskImmediate: createTask: (CancellationToken -> Task<'T>) -> Async<'T> + + /// Creates an asynchronous computation that passes the ambient Async.CancellationToken to + /// createTask, and then awaits the resulting task. + /// + /// A function that accepts a CancellationToken and returns a Task. + /// + /// The cancellation token of the enclosing async computation is automatically passed to + /// createTask, propagating cancellation naturally to the task without requiring manual token capture. + /// + /// The resulting task is awaited using ; + /// exception unwrapping and cancellation handling are as per that overload. + /// + /// Starting Async Computations + /// + /// + /// async { + /// do! Async.StartTaskImmediate(fun ct -> File.WriteAllTextAsync("file.txt", "hello", ct)) + /// } + /// + /// + static member StartTaskImmediate: createTask: (CancellationToken -> Task) -> Async + +#if NETSTANDARD2_1 + /// Creates an asynchronous computation that passes the ambient Async.CancellationToken to + /// createTask, and then awaits the resulting ValueTask, returning its result. + /// + /// A function that accepts a CancellationToken and returns a ValueTask<'T>. + /// + /// The cancellation token of the enclosing async computation is automatically passed to + /// createTask, propagating cancellation naturally to the task without requiring manual token capture. + /// + /// The resulting task is awaited using ; + /// exception unwrapping and cancellation handling are as per that overload. + /// + /// Starting Async Computations + static member StartTaskImmediate: createTask: (CancellationToken -> ValueTask<'T>) -> Async<'T> + + /// Creates an asynchronous computation that passes the ambient Async.CancellationToken to + /// createTask, and then awaits the resulting ValueTask. + /// + /// A function that accepts a CancellationToken and returns a ValueTask. + /// + /// The cancellation token of the enclosing async computation is automatically passed to + /// createTask, propagating cancellation naturally to the task without requiring manual token capture. + /// + /// The resulting task is awaited using ; + /// exception unwrapping and cancellation handling are as per that overload. + /// + /// Starting Async Computations + static member StartTaskImmediate: createTask: (CancellationToken -> ValueTask) -> Async + +#endif /// /// Creates an asynchronous computation that will sleep for the given time. This is scheduled /// using a System.Threading.Timer object. The operation will not block operating system threads @@ -1286,7 +1378,11 @@ namespace Microsoft.FSharp.Control /// 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 + /// + ///

For the common case where you are running a Task within an Asynchronous Computation, + /// see StartTaskImmediate, which surfaces the ambient CancellationToken + /// so that it can be passed to the Task being started.

+ ///

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.

@@ -1330,6 +1426,53 @@ namespace Microsoft.FSharp.Control and ^Awaiter: (member get_IsCompleted: unit -> bool) and ^Awaiter: (member GetResult: unit -> 'T) + /// Creates an asynchronous computation that passes the ambient Async.CancellationToken to + /// createTask, and then awaits the resulting task-like value. + /// + /// A function that accepts a CancellationToken and returns a task-like value + /// satisfying the GetAwaiter pattern. + /// + /// The value returned by createTask must satisfy the GetAwaiter pattern: it must have a + /// GetAwaiter() method returning an awaiter implementing + /// + /// with IsCompleted and GetResult() members. + /// + /// This overload uses statically resolved type parameters (SRTP) so it can accept factories returning + /// any task-like type, including YieldAwaitable (from Task.Yield()) and + /// ConfiguredTaskAwaitable (from task.ConfigureAwait(false)). + /// The specific overloads for , , + /// and + /// are preferred when the factory return type is known. + /// + /// Starting Async Computations + /// + /// + /// // Straightforward: factory returns Task<string>, which is handled by + /// // the specific Task<'T> overload of StartTaskImmediate (not this one). + /// let fetchPlain (url: string) = + /// Async.StartTaskImmediate(fun ct -> + /// httpClient.GetStringAsync(url, ct)) // returns Task<string> + /// + /// // Adding ConfigureAwait(false) to the mix yields a ConfiguredTaskAwaitable<string>, + /// // which has no specific overload — this SRTP overload handles it. + /// let fetchConfigured (url: string) = + /// Async.StartTaskImmediate(fun ct -> + /// httpClient.GetStringAsync(url, ct).ConfigureAwait(false)) + /// + /// async { + /// let! html = fetchConfigured "https://example.com" + /// printfn $"Downloaded {html.Length} chars" + /// } |> Async.RunSynchronouslyImmediate + /// + /// + [] + static member inline StartTaskImmediate< ^TaskLike, ^Awaiter, 'T> : + createTask: (CancellationToken -> ^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 1f6241a57e3..da9227f2a63 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl @@ -633,6 +633,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn 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.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.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], Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,TTaskLike]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.Static[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,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]) @@ -657,6 +659,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Mic 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) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(System.TimeSpan) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] StartTaskImmediate(Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToContext(System.Threading.SynchronizationContext) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToNewThread() Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToThreadPool() @@ -677,6 +680,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,T](TArg1, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg1,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[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[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] FromContinuations[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit],Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.Unit],Microsoft.FSharp.Core.FSharpFunc`2[System.OperationCanceledException,Microsoft.FSharp.Core.Unit]],Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] StartTaskImmediate[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] TryCancelled[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.OperationCanceledException,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.FSharpAsync: System.Threading.CancellationToken DefaultCancellationToken Microsoft.FSharp.Control.FSharpAsync: System.Threading.CancellationToken get_DefaultCancellationToken() 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 0b025a942de..b0d3ebc9665 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -633,6 +633,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn 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.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.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], Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,TTaskLike]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.Static[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,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]) @@ -657,6 +659,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Mic 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) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(System.TimeSpan) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] StartTaskImmediate(Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToContext(System.Threading.SynchronizationContext) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToNewThread() Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToThreadPool() @@ -677,6 +680,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,T](TArg1, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg1,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[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[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] FromContinuations[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit],Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.Unit],Microsoft.FSharp.Core.FSharpFunc`2[System.OperationCanceledException,Microsoft.FSharp.Core.Unit]],Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] StartTaskImmediate[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] TryCancelled[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.OperationCanceledException,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.FSharpAsync: System.Threading.CancellationToken DefaultCancellationToken Microsoft.FSharp.Control.FSharpAsync: System.Threading.CancellationToken get_DefaultCancellationToken() 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 9496d1dfe2b..174a09bf2c0 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl @@ -635,6 +635,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn 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.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.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], Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,TTaskLike]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.Static[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,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]) @@ -660,6 +662,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Mic 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) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(System.TimeSpan) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] StartTaskImmediate(Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] StartTaskImmediate(Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.ValueTask]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToContext(System.Threading.SynchronizationContext) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToNewThread() Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToThreadPool() @@ -681,6 +685,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,T](TArg1, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg1,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[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[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] FromContinuations[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit],Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.Unit],Microsoft.FSharp.Core.FSharpFunc`2[System.OperationCanceledException,Microsoft.FSharp.Core.Unit]],Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] StartTaskImmediate[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task`1[T]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] StartTaskImmediate[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.ValueTask`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] TryCancelled[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.OperationCanceledException,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.FSharpAsync: System.Threading.CancellationToken DefaultCancellationToken Microsoft.FSharp.Control.FSharpAsync: System.Threading.CancellationToken get_DefaultCancellationToken() 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 b1a377ea33b..ffe81d5ba6b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -635,6 +635,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn 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.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.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], Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,TTaskLike]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.StartTaskImmediate.Static[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,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]) @@ -660,6 +662,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Mic 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) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(System.TimeSpan) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] StartTaskImmediate(Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] StartTaskImmediate(Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.ValueTask]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToContext(System.Threading.SynchronizationContext) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToNewThread() Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] SwitchToThreadPool() @@ -676,6 +680,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] 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] StartTaskImmediate[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.Task`1[T]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] StartTaskImmediate[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,System.Threading.Tasks.ValueTask`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]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,T](TArg1, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg1,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 2ce61e65968..e77154860d1 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 @@ -11,7 +11,7 @@ open Xunit open System.Threading open System.Threading.Tasks -// Cancels default token. +// Test affects global state via Async.CancelDefaultToken [] module AsyncType = @@ -40,6 +40,7 @@ module AsyncType = async { return () } |> expect Success +// Multiple tests affect global state via Async.CancelDefaultToken [] type AsyncType() = @@ -54,20 +55,21 @@ type AsyncType() = let result = t.Wait(TimeSpan.FromSeconds(30.0)) Assert.True(result, "Task did not finish after waiting for 30 seconds.") - [] - member _.AsyncRunSynchronouslyReusesThreadPoolThread() = + [] + member _.AsyncRunSynchronouslyReusesThreadPoolThread(immediate) = + let run a = if immediate then Async.RunSynchronouslyImmediate(a) else Async.RunSynchronously(a) let action _ = async { return async { return Thread.CurrentThread.ManagedThreadId } - |> Async.RunSynchronously + |> run } // This test needs approximately 1000 ThreadPool threads // if Async.RunSynchronously doesn't reuse them. let usedThreads = Seq.init 1000 action |> Async.Parallel - |> Async.RunSynchronously + |> run |> Set.ofArray printfn $"RunSynchronously used {usedThreads.Count} threads. Environment.ProcessorCount is {Environment.ProcessorCount}." // Some arbitrary large number but in practice it should not use more threads than there are CPU cores. @@ -82,7 +84,7 @@ type AsyncType() = match sleepType with | "int32" -> Async.Sleep(10000000) | "timespan" -> Async.Sleep(10000000.0 |> TimeSpan.FromMilliseconds) - | unknown -> raise (NotImplementedException(unknown)) + | unknown -> failwith $"Unknown {unknown}" let mutable result = "" use cts = new CancellationTokenSource() Async.StartWithContinuations(computation, @@ -91,7 +93,7 @@ type AsyncType() = (fun _ -> result <- "Cancel"), cts.Token) cts.Cancel() - Async.Sleep(1000) |> Async.RunSynchronously + Async.Sleep(1000) |> Async.RunSynchronouslyImmediate Assert.AreEqual("Cancel", result) ) @@ -104,7 +106,7 @@ type AsyncType() = match sleepType with | "int32" -> Async.Sleep(10) | "timespan" -> Async.Sleep(10.0 |> TimeSpan.FromMilliseconds) - | unknown -> raise (NotImplementedException(unknown)) + | unknown -> failwith $"Unknown {unknown}" for i in 1..100 do let mutable result = "" use completedEvent = new ManualResetEvent(false) @@ -129,16 +131,16 @@ type AsyncType() = do! match sleepType with | "int32" -> Async.Sleep(-100) | "timespan" -> Async.Sleep(-100.0 |> TimeSpan.FromMilliseconds) - | unknown -> raise (NotImplementedException(unknown)) + | unknown -> failwith $"Unknown {unknown}" failwith "Expected ArgumentOutOfRangeException" with | :? ArgumentOutOfRangeException -> () - } |> Async.RunSynchronously + } |> Async.RunSynchronouslyImmediate [] member _.AsyncSleepInfinitely() = ignoreSynchCtx (fun () -> - let computation = Async.Sleep(System.Threading.Timeout.Infinite) + let computation = Async.Sleep(Timeout.Infinite) let result = TaskCompletionSource() use cts = new CancellationTokenSource(TimeSpan.FromSeconds(1.0)) // there's a long way from 1 sec to infinity, but it'll have to do. Async.StartWithContinuations(computation, @@ -146,7 +148,7 @@ type AsyncType() = (fun _ -> result.TrySetResult("Exception") |> ignore), (fun _ -> result.TrySetResult("Cancel") |> ignore), cts.Token) - let result = result.Task |> Async.AwaitTask |> Async.RunSynchronously + let result = result.Task |> Async.Await |> Async.RunSynchronouslyImmediate Assert.AreEqual("Cancel", result) ) @@ -156,7 +158,7 @@ type AsyncType() = let a = async { return s } let t : Task = Async.StartAsTask a waitForCompletion t - Assert.True (t.IsCompleted) + Assert.True(t.IsCompleted) Assert.AreEqual(s, t.Result) [] @@ -166,7 +168,7 @@ type AsyncType() = let doSpinloop () = while spinloop do () let a = async { asyncStarted.Set() - cts.CancelAfter (100) + cts.CancelAfter(100) doSpinloop() } @@ -179,7 +181,7 @@ type AsyncType() = // Should not finish, we don't eagerly mark the task done just because it's been signaled to cancel. try let result = t.Wait(1000) - Assert.False (result) + Assert.False(result) with :? AggregateException -> Assert.Fail "Task should not finish, yet" spinloop <- false @@ -198,8 +200,8 @@ type AsyncType() = [] member _.``AwaitTask ignores Async cancellation`` () = let cts = new CancellationTokenSource() - let tcs = new TaskCompletionSource() - let innerTcs = new TaskCompletionSource() + let tcs = TaskCompletionSource() + let innerTcs = TaskCompletionSource() let a = innerTcs.Task |> Async.AwaitTask Async.StartWithContinuations(a, tcs.SetResult, tcs.SetException, ignore >> tcs.SetCanceled, cts.Token) @@ -207,7 +209,7 @@ type AsyncType() = cts.CancelAfter(100) try let result = tcs.Task.Wait(300) - Assert.False (result) + Assert.False(result) with :? AggregateException -> Assert.Fail "Should not finish, yet" innerTcs.SetResult () @@ -232,12 +234,12 @@ type AsyncType() = let cancelled = try - Async.RunSynchronously(a, cancellationToken = cts.Token) |> ignore + Async.RunSynchronouslyImmediate(a, cancellationToken = cts.Token) |> ignore false with :? OperationCanceledException as o -> true | _ -> false - Assert.True (cancelled, "Task is not cancelled") + Assert.True(cancelled, "Task is not cancelled") [] member _.ExceptionPropagatesToTask () = @@ -251,7 +253,7 @@ type AsyncType() = t.Wait() with e -> exceptionThrown <- true - Assert.True (t.IsFaulted) + Assert.True(t.IsFaulted) Assert.True(exceptionThrown) [] @@ -268,7 +270,7 @@ type AsyncType() = try waitForCompletion t with e -> exceptionThrown <- true - Assert.True (exceptionThrown) + Assert.True(exceptionThrown) Assert.True(t.IsCanceled) [] @@ -291,7 +293,7 @@ type AsyncType() = try t.Wait() with e -> exceptionThrown <- true - Assert.True (exceptionThrown) + Assert.True(exceptionThrown) Assert.True(t.IsCanceled) Assert.True(cancelled) @@ -301,7 +303,7 @@ type AsyncType() = let a = async { return s } let t : Task = Async.StartImmediateAsTask a waitForCompletion t - Assert.True (t.IsCompleted) + Assert.True(t.IsCompleted) Assert.AreEqual(s, t.Result) [] @@ -310,7 +312,7 @@ type AsyncType() = let a = async { return s } let t = Async.StartImmediateAsTask a waitForCompletion t - Assert.True (t.IsCompleted) + Assert.True(t.IsCompleted) Assert.AreEqual(s, t.Result) @@ -325,7 +327,7 @@ type AsyncType() = t.Wait() with e -> exceptionThrown <- true - Assert.True (t.IsFaulted) + Assert.True(t.IsFaulted) Assert.True(exceptionThrown) [] @@ -340,7 +342,7 @@ type AsyncType() = try t.Wait() with e -> exceptionThrown <- true - Assert.True (exceptionThrown) + Assert.True(exceptionThrown) Assert.True(t.IsCanceled) [] @@ -363,7 +365,7 @@ type AsyncType() = try t.Wait() with e -> exceptionThrown <- true - Assert.True (exceptionThrown) + Assert.True(exceptionThrown) Assert.True(t.IsCanceled) Assert.True(cancelled) @@ -375,8 +377,7 @@ type AsyncType() = let! s1 = t |> if newAwait then Async.Await else Async.AwaitTask return s = s1 } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.AwaitTaskCancellation(newAwait: bool) = @@ -388,8 +389,7 @@ type AsyncType() = return false with :? OperationCanceledException -> return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.AwaitCompletedTask() = @@ -399,8 +399,7 @@ type AsyncType() = let threadIdAfter = Thread.CurrentThread.ManagedThreadId return threadIdBefore = threadIdAfter } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.AwaitTaskCancellationUntyped(newAwait: bool) = @@ -412,8 +411,7 @@ type AsyncType() = return false with :? OperationCanceledException -> return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.TaskAsyncValueException(newAwait: bool) = @@ -423,8 +421,7 @@ type AsyncType() = return false with e -> return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.TaskAsyncValueCancellation(newAwait: bool) = @@ -457,7 +454,7 @@ type AsyncType() = do! t |> if newAwait then Async.Await else Async.AwaitTask return true } - let ok = Async.RunSynchronously a + let ok = Async.RunSynchronouslyImmediate a Assert.True(hasBeenCalled && ok) [] @@ -469,8 +466,7 @@ type AsyncType() = return false with e -> return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.NonGenericTaskAsyncValueCancellation(newAwait: bool) = @@ -499,7 +495,7 @@ type AsyncType() = let cts = new CancellationTokenSource() let token = cts.Token let mutable hasThrown = false - token.Register(fun () -> ewh.Set() |> ignore) |> ignore + token.Register(fun () -> ewh.Set()) |> ignore let a = async { try while true do token.ThrowIfCancellationRequested() @@ -519,7 +515,7 @@ type AsyncType() = return! loop(x+1) } - try Async.RunSynchronously (loop 0) + try Async.RunSynchronously(loop 0) hasThrown <- false with Failure "finish" -> hasThrown <- true @@ -561,8 +557,7 @@ type AsyncType() = return false with :? AggregateException as ae -> return ae.InnerExceptions.Count = 2 } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.``Await and AwaitTask(Task) valid AggregateException is surfaced``(newAwait) = @@ -574,8 +569,7 @@ type AsyncType() = return false with :? AggregateException as ae -> return ae.InnerExceptions.Count = 2 } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) (* Async.Await behavioral differences @@ -591,8 +585,7 @@ type AsyncType() = return false with :? AggregateException -> return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) // ... whereas Async.Await(Task) surfaces the inner exception directly. [] @@ -604,8 +597,7 @@ type AsyncType() = return false with :? ArgumentException as ae -> return ae.Message = "original" } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) // Async.AwaitTask(Task<'T>) surfaces the wrapping AggregateException ... [] @@ -617,8 +609,7 @@ type AsyncType() = return false with :? AggregateException -> return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) // ... whereas Async.Await(Task<'T>) surfaces the inner exception directly. [] @@ -630,28 +621,87 @@ type AsyncType() = return false with :? ArgumentException as ae -> return ae.Message = "original" } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) (* 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)) + let! v = Async.Await(Task.FromResult(42)) return v = 42 } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.``Await(Task) happy path``() = let a = async { - do! Async.Await(System.Threading.Tasks.Task.CompletedTask) + do! Async.Await(Task.CompletedTask) return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) + + (* StartTaskImmediate(Task/Task<'T>) *) + + [] + member _.``StartTaskImmediate(Task<'T>) flows result``() = + let a = async { + let! v = Async.StartTaskImmediate(fun _ct -> Task.result 42) + return v = 42 + } + Assert.True(Async.RunSynchronouslyImmediate a) + + [] + member _.``StartTaskImmediate(Task) happy path``() = + let mutable called = false + let a = async { + do! Async.StartTaskImmediate(fun _ct -> task { called <- true }) + } + Async.RunSynchronouslyImmediate a + Assert.True called + + [] + member _.``StartTaskImmediate flows CancellationToken``() = + let cts = new CancellationTokenSource() + let mutable capturedCt = CancellationToken.None + let a = async { + do! Async.StartTaskImmediate(fun ct -> task { capturedCt <- ct }) + } + Async.RunSynchronouslyImmediate(a, cancellationToken = cts.Token) + Assert.Equal(cts.Token, capturedCt) + + [] + member _.``StartTaskImmediate(Task<'T>) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.StartTaskImmediate(fun _ct -> tcs.Task) + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + Assert.True(Async.RunSynchronouslyImmediate a) + + [] + member _.``StartTaskImmediate(Task) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try do! Async.StartTaskImmediate(fun _ct -> tcs.Task :> Task) + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + Assert.True(Async.RunSynchronouslyImmediate a) + + [] + member _.``StartTaskImmediate(Task<'T>) cancellation raises TaskCanceledException``() = + let tcs = TaskCompletionSource() + tcs.SetCanceled() + let a = async { + try let! _ = Async.StartTaskImmediate(fun _ct -> tcs.Task) + return false + with :? TaskCanceledException -> return true + } + Assert.True(Async.RunSynchronouslyImmediate a) #if !NETFRAMEWORK (* Await(ValueTask and ValueTask<'T>) overloads coverage of mainline behaviors *) @@ -662,8 +712,7 @@ type AsyncType() = do! Async.Await(ValueTask()) return true } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True (Async.RunSynchronouslyImmediate a) [] member _.``Await(ValueTask<'T>) happy path``() = @@ -671,8 +720,7 @@ type AsyncType() = let! v = Async.Await(ValueTask(42)) return v = 42 } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True (Async.RunSynchronouslyImmediate a) [] member _.``Await(ValueTask) exception unwraps``() = @@ -684,8 +732,7 @@ type AsyncType() = return false with :? ArgumentException as ae -> return ae.Message = "original" } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] member _.``Await(ValueTask<'T>) exception unwraps``() = @@ -696,11 +743,51 @@ type AsyncType() = return false with :? ArgumentException as ae -> return ae.Message = "original" } - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) + + (* StartTaskImmediate(ValueTask/ValueTask<'T>) *) + + [] + member _.``StartTaskImmediate(ValueTask<'T>) flows result``() = + let a = async { + let! v = Async.StartTaskImmediate(fun _ct -> ValueTask(42)) + return v = 42 + } + Assert.True(Async.RunSynchronouslyImmediate a) + + [] + member _.``StartTaskImmediate(ValueTask) happy path``() = + let a = async { + do! Async.StartTaskImmediate(fun _ct -> ValueTask()) + return true + } + Assert.True(Async.RunSynchronouslyImmediate a) + + [] + member _.``StartTaskImmediate(ValueTask<'T>) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try + let! _ = Async.StartTaskImmediate(fun _ct -> ValueTask(tcs.Task)) + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + Assert.True(Async.RunSynchronouslyImmediate a) + + [] + member _.``StartTaskImmediate(ValueTask) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try + do! Async.StartTaskImmediate(fun _ct -> ValueTask(tcs.Task :> Task)) + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + Assert.True(Async.RunSynchronouslyImmediate a) #endif -[] module AsyncTaskLikeAwaitTests = // Minimal custom task-like type wrapping Task<'T> @@ -713,20 +800,20 @@ module AsyncTaskLikeAwaitTests = [] let ``Await(task-like) happy path with result``() = - let result = + let a = async { - let! v = Async.Await(MyTask(Task.FromResult 99)) + let! v = Async.Await(MyTask(Task.FromResult 42)) return v } - |> Async.RunSynchronously - Assert.Equal(99, result) + Assert.Equal(42, Async.RunSynchronouslyImmediate a) + [] let ``Await(task-like) happy path unit``() = async { do! Async.Await(MyUnitTask(Task.CompletedTask)) } - |> Async.RunSynchronously + |> Async.RunSynchronouslyImmediate [] let ``Await(task-like) deferred completion``() = @@ -778,8 +865,7 @@ module AsyncTaskLikeAwaitTests = return e.Message = "boom" } tcs.SetException(InvalidOperationException "boom") - let ok = Async.RunSynchronously a - Assert.True ok + Assert.True(Async.RunSynchronouslyImmediate a) [] let ``Await(YieldAwaitable) yields and resumes``() = @@ -790,7 +876,7 @@ module AsyncTaskLikeAwaitTests = do! Async.Await(Task.Yield()) after <- true } - |> Async.RunSynchronously + |> Async.RunSynchronouslyImmediate Assert.True(before && after) [] @@ -801,10 +887,51 @@ module AsyncTaskLikeAwaitTests = let! v = Async.Await(Task.FromResult(42).ConfigureAwait(false)) return v } - |> Async.RunSynchronously + |> Async.RunSynchronouslyImmediate Assert.Equal(42, result) -[] +module AsyncStartTaskImmediateTaskLikeTests = + + [] + let ``StartTaskImmediate(YieldAwaitable factory) yields and resumes``() = + // Task.Yield() returns a struct YieldAwaitable — exercises the struct-awaiter SRTP path. + let mutable before, after = false, false + + async { + before <- true + do! Async.StartTaskImmediate(fun _ -> Task.Yield()) + after <- true + } + |> Async.RunSynchronouslyImmediate + + Assert.True(before && after) + + [] + let ``StartTaskImmediate(ConfiguredTaskAwaitable factory) returns result``() = + // ConfigureAwait(false) returns a ConfiguredTaskAwaitable — a common real-world task-like. + let result = + async { + return! Async.StartTaskImmediate(fun _ -> Task.FromResult(42).ConfigureAwait(false)) + } + |> Async.RunSynchronouslyImmediate + + Assert.Equal(42, result) + + [] + let ``StartTaskImmediate flows CancellationToken``() = + // The factory receives the ambient cancellation token from the enclosing async. + let mutable capturedCt = CancellationToken.None + use cts = new CancellationTokenSource() + + let a = async { + do! Async.StartTaskImmediate(fun ct -> + capturedCt <- ct + Task.CompletedTask.ConfigureAwait(false)) + } + Async.RunSynchronouslyImmediate(a, cancellationToken = cts.Token) + + Assert.Equal(cts.Token, capturedCt) + module AsyncAwaitStackTraceTests = open System.Runtime.CompilerServices @@ -829,9 +956,9 @@ module AsyncAwaitStackTraceTests = // 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. + // the stack trace rather than rethrowing synchronously and/or potentially truncating it. let runAndCaptureException (computation: Async) : exn = - // TODO swap in usage of Async.RunSynchronouslyImmediate + // TODO swap in usage of Async.RunSynchronouslyImmediate and add characterization of the stack trace behavior there as well let t = Async.StartImmediateAsTask computation let ae = Assert.Throws(fun () -> t.Wait()) ae.InnerException @@ -888,5 +1015,6 @@ module AsyncAwaitStackTraceTests = 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) + // 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