From 0fdc10c74bd2c95250f7336d6020040d96dce79c Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Mon, 25 May 2026 18:36:54 +0100
Subject: [PATCH 01/12] Apply even more feedback goodness
---
docs/release-notes/.FSharp.Core/11.0.100.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index 884146bc4e4..baef8f73036 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -11,3 +11,4 @@
* `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.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes ValueTask support, and a SRTP-based overload accepting any task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785))
From 7da92ce12df82b52f4fee169d15fab009b3078cf Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Fri, 22 May 2026 14:32:34 +0100
Subject: [PATCH 02/12] feat(Async): StartTaskImmediate
# Conflicts:
# tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs
---
docs/release-notes/.FSharp.Core/11.0.100.md | 3 +
src/FSharp.Core/async.fs | 13 +++
src/FSharp.Core/async.fsi | 73 ++++++++++++
...p.Core.SurfaceArea.netstandard20.debug.bsl | 2 +
...Core.SurfaceArea.netstandard20.release.bsl | 2 +
...p.Core.SurfaceArea.netstandard21.debug.bsl | 4 +
...Core.SurfaceArea.netstandard21.release.bsl | 4 +
.../Microsoft.FSharp.Control/AsyncType.fs | 110 ++++++++++++++++++
8 files changed, 211 insertions(+)
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index baef8f73036..25cd0e53a08 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -12,3 +12,6 @@
* 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.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes ValueTask support, and a SRTP-based overload accepting any task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785))
+* Add `Async.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Also adds an SRTP 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))
+* 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))
+* Add `Async.StartTaskImmediate`, which captures the ambient `CancellationToken`, passes it to a task factory, then awaits the result using `Async.Await` semantics. Overloads for `Task`, `Task<'T>`, `ValueTask`, and `ValueTask<'T>`. ([Language Suggestion #1284](https://github.com/fsharp/fslang-suggestions/issues/1284))
diff --git a/src/FSharp.Core/async.fs b/src/FSharp.Core/async.fs
index e73dc4aa230..1ffb1de64a8 100644
--- a/src/FSharp.Core/async.fs
+++ b/src/FSharp.Core/async.fs
@@ -2268,6 +2268,19 @@ 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
diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi
index 2171f75164a..9974dfae11c 100644
--- a/src/FSharp.Core/async.fsi
+++ b/src/FSharp.Core/async.fsi
@@ -983,6 +983,79 @@ namespace Microsoft.FSharp.Control
static member Await: task: ValueTask -> Async
#endif
+ /// Creates an asynchronous computation that captures the ambient CancellationToken, passes it 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 captures the ambient CancellationToken, passes it 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 captures the ambient CancellationToken, passes it 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 captures the ambient CancellationToken, passes it 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
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..52bcff8a83c 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl
@@ -657,6 +657,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 +678,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..6e38a636871 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl
@@ -657,6 +657,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 +678,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..6a3dec18e5f 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl
@@ -660,6 +660,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 +683,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..89fbc8dc499 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl
@@ -660,6 +660,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 +678,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..1becd4e782b 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
@@ -653,6 +653,74 @@ type AsyncType() =
let ok = Async.RunSynchronously a
Assert.True ok
+ (* StartTaskImmediate(Task/Task<'T>) coverage *)
+
+ []
+ member _.``StartTaskImmediate(Task<'T>) flows result``() =
+ let a = async {
+ let! v = Async.StartTaskImmediate(fun _ct -> Task.FromResult(42))
+ return v = 42
+ }
+ Assert.True(Async.RunSynchronously a)
+
+ []
+ member _.``StartTaskImmediate(Task) happy path``() =
+ let mutable called = false
+ let a = async {
+ do! Async.StartTaskImmediate(fun _ct -> Task.Run(fun () -> called <- true))
+ return true
+ }
+ let ok = Async.RunSynchronously a
+ Assert.True(called && ok)
+
+ []
+ member _.``StartTaskImmediate flows CancellationToken``() =
+ let cts = new CancellationTokenSource()
+ let capturedCt = ref CancellationToken.None
+ let a = async {
+ do! Async.StartTaskImmediate(fun ct ->
+ capturedCt.Value <- ct
+ Task.CompletedTask)
+ }
+ Async.RunSynchronously(a, cancellationToken = cts.Token)
+ Assert.Equal(cts.Token, capturedCt.Value)
+
+ []
+ 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"
+ }
+ let ok = Async.RunSynchronously a
+ Assert.True ok
+
+ []
+ 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"
+ }
+ let ok = Async.RunSynchronously a
+ Assert.True ok
+
+ []
+ 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
+ }
+ let ok = Async.RunSynchronously a
+ Assert.True ok
+
#if !NETFRAMEWORK
(* Await(ValueTask and ValueTask<'T>) overloads coverage of mainline behaviors *)
@@ -698,6 +766,48 @@ type AsyncType() =
}
let ok = Async.RunSynchronously a
Assert.True ok
+
+ (* StartTaskImmediate(ValueTask/ValueTask<'T>) coverage *)
+
+ []
+ member _.``StartTaskImmediate(ValueTask<'T>) flows result``() =
+ let a = async {
+ let! v = Async.StartTaskImmediate(fun _ct -> ValueTask(42))
+ return v = 42
+ }
+ Assert.True(Async.RunSynchronously a)
+
+ []
+ member _.``StartTaskImmediate(ValueTask) happy path``() =
+ let a = async {
+ do! Async.StartTaskImmediate(fun _ct -> ValueTask())
+ return true
+ }
+ Assert.True(Async.RunSynchronously 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.RunSynchronously 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.RunSynchronously a)
#endif
[]
From 1ee88256e27a88b462a7aca1afdec42af06467ea Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Mon, 25 May 2026 17:55:41 +0100
Subject: [PATCH 03/12] Add Tasklike overload
---
src/FSharp.Core/async.fs | 19 +++++--
src/FSharp.Core/async.fsi | 37 ++++++++++++++
...p.Core.SurfaceArea.netstandard20.debug.bsl | 2 +
...Core.SurfaceArea.netstandard20.release.bsl | 2 +
...p.Core.SurfaceArea.netstandard21.debug.bsl | 2 +
...Core.SurfaceArea.netstandard21.release.bsl | 2 +
.../Microsoft.FSharp.Control/AsyncType.fs | 49 +++++++++++++++++--
7 files changed, 106 insertions(+), 7 deletions(-)
diff --git a/src/FSharp.Core/async.fs b/src/FSharp.Core/async.fs
index 1ffb1de64a8..bebe104e830 100644
--- a/src/FSharp.Core/async.fs
+++ b/src/FSharp.Core/async.fs
@@ -2269,16 +2269,17 @@ type Async =
#endif
static member StartTaskImmediate(createTask: CancellationToken -> Task<'T>) : Async<'T> =
- CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
+ CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
+
static member StartTaskImmediate(createTask: CancellationToken -> Task) : Async =
- CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
+ CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
#if NETSTANDARD2_1
static member StartTaskImmediate(createTask: CancellationToken -> ValueTask<'T>) : Async<'T> =
- CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
+ CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
static member StartTaskImmediate(createTask: CancellationToken -> ValueTask) : Async =
- CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
+ CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
#endif
module AsyncTaskLikeExtensions =
@@ -2309,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 9974dfae11c..25360a65682 100644
--- a/src/FSharp.Core/async.fsi
+++ b/src/FSharp.Core/async.fsi
@@ -1403,6 +1403,43 @@ 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 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
+ ///
+ ///
+ /// async {
+ /// // Yield to other work and resume, without capturing a SynchronizationContext
+ /// do! Async.StartTaskImmediate(fun _ct -> Task.Yield())
+ /// let! v = Async.StartTaskImmediate(fun _ct -> Task.FromResult(42).ConfigureAwait(false))
+ /// printfn $"Result: {v}"
+ /// } |> Async.RunSynchronously
+ ///
+ ///
+ []
+ 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 52bcff8a83c..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])
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 6e38a636871..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])
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 6a3dec18e5f..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])
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 89fbc8dc499..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])
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 1becd4e782b..cafc916cb54 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
@@ -676,14 +676,14 @@ type AsyncType() =
[]
member _.``StartTaskImmediate flows CancellationToken``() =
let cts = new CancellationTokenSource()
- let capturedCt = ref CancellationToken.None
+ let mutable capturedCt = CancellationToken.None
let a = async {
do! Async.StartTaskImmediate(fun ct ->
- capturedCt.Value <- ct
+ capturedCt <- ct
Task.CompletedTask)
}
Async.RunSynchronously(a, cancellationToken = cts.Token)
- Assert.Equal(cts.Token, capturedCt.Value)
+ Assert.Equal(cts.Token, capturedCt)
[]
member _.``StartTaskImmediate(Task<'T>) exception unwraps``() =
@@ -914,6 +914,49 @@ module AsyncTaskLikeAwaitTests =
|> Async.RunSynchronously
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.RunSynchronously
+
+ 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.RunSynchronously
+
+ 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.RunSynchronously(a, cancellationToken = cts.Token)
+
+ Assert.Equal(cts.Token, capturedCt)
+
[]
module AsyncAwaitStackTraceTests =
From 6d00748ab04122bf67305b2d5f7eb90c2637c402 Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Mon, 25 May 2026 18:23:54 +0100
Subject: [PATCH 04/12] Polish xmldoc
---
docs/release-notes/.FSharp.Core/11.0.100.md | 4 +--
src/FSharp.Core/async.fsi | 28 ++++++++++++++-------
2 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index 25cd0e53a08..916fe1cda95 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -12,6 +12,4 @@
* 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.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes ValueTask support, and a SRTP-based overload accepting any task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785))
-* Add `Async.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Also adds an SRTP 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))
-* 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))
-* Add `Async.StartTaskImmediate`, which captures the ambient `CancellationToken`, passes it to a task factory, then awaits the result using `Async.Await` semantics. Overloads for `Task`, `Task<'T>`, `ValueTask`, and `ValueTask<'T>`. ([Language Suggestion #1284](https://github.com/fsharp/fslang-suggestions/issues/1284))
+* Add `Async.StartTaskImmediate`, which propagates the ambient `Async.CancellationToken`, to a task factory, then awaits 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))
diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi
index 25360a65682..3836fcf54f5 100644
--- a/src/FSharp.Core/async.fsi
+++ b/src/FSharp.Core/async.fsi
@@ -983,7 +983,7 @@ namespace Microsoft.FSharp.Control
static member Await: task: ValueTask -> Async
#endif
- /// Creates an asynchronous computation that captures the ambient CancellationToken, passes it to
+ /// 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>.
@@ -1005,7 +1005,7 @@ namespace Microsoft.FSharp.Control
///
static member StartTaskImmediate: createTask: (CancellationToken -> Task<'T>) -> Async<'T>
- /// Creates an asynchronous computation that captures the ambient CancellationToken, passes it to
+ /// 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.
@@ -1027,7 +1027,7 @@ namespace Microsoft.FSharp.Control
static member StartTaskImmediate: createTask: (CancellationToken -> Task) -> Async
#if NETSTANDARD2_1
- /// Creates an asynchronous computation that captures the ambient CancellationToken, passes it to
+ /// Creates an asynchronous computation that captures 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>.
@@ -1041,7 +1041,7 @@ namespace Microsoft.FSharp.Control
/// Starting Async Computations
static member StartTaskImmediate: createTask: (CancellationToken -> ValueTask<'T>) -> Async<'T>
- /// Creates an asynchronous computation that captures the ambient CancellationToken, passes it to
+ /// Creates an asynchronous computation that passes the ambient CancellationToken to
/// createTask, and then awaits the resulting ValueTask.
///
/// A function that accepts a CancellationToken and returns a ValueTask.
@@ -1403,7 +1403,7 @@ 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 CancellationToken, to
+ /// Creates an asynchronous computation that passes the ambient CancellationToken to
/// createTask, and then awaits the resulting task-like value.
///
/// A function that accepts a CancellationToken and returns a task-like value
@@ -1424,11 +1424,21 @@ namespace Microsoft.FSharp.Control
/// 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 {
- /// // Yield to other work and resume, without capturing a SynchronizationContext
- /// do! Async.StartTaskImmediate(fun _ct -> Task.Yield())
- /// let! v = Async.StartTaskImmediate(fun _ct -> Task.FromResult(42).ConfigureAwait(false))
- /// printfn $"Result: {v}"
+ /// let! html = fetchConfigured "https://example.com"
+ /// printfn $"Downloaded {html.Length} chars"
/// } |> Async.RunSynchronously
///
///
From cf9aee81da07025cbb7d45dbded8a708fd67a6e6 Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Tue, 26 May 2026 23:42:56 +0100
Subject: [PATCH 05/12] Address copilot review
---
docs/release-notes/.FSharp.Core/11.0.100.md | 2 +-
src/FSharp.Core/async.fsi | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index 916fe1cda95..e18e415092a 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -12,4 +12,4 @@
* 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.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes ValueTask support, and a SRTP-based overload accepting any task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785))
-* Add `Async.StartTaskImmediate`, which propagates the ambient `Async.CancellationToken`, to a task factory, then awaits 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))
+* Add `Async.StartTaskImmediate`, which propagates the ambient `Async.CancellationToken` to a task factory, then awaits 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))
diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi
index 3836fcf54f5..123c0336d34 100644
--- a/src/FSharp.Core/async.fsi
+++ b/src/FSharp.Core/async.fsi
@@ -1027,7 +1027,7 @@ namespace Microsoft.FSharp.Control
static member StartTaskImmediate: createTask: (CancellationToken -> Task) -> Async
#if NETSTANDARD2_1
- /// Creates an asynchronous computation that captures the ambient Async.CancellationToken to
+ /// 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>.
@@ -1041,7 +1041,7 @@ namespace Microsoft.FSharp.Control
/// Starting Async Computations
static member StartTaskImmediate: createTask: (CancellationToken -> ValueTask<'T>) -> Async<'T>
- /// Creates an asynchronous computation that passes the ambient CancellationToken to
+ /// 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.
@@ -1403,7 +1403,7 @@ 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 CancellationToken to
+ /// 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
From 19749b0ddc316d784fa67a4807b4ec3e4b0466a4 Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Thu, 13 Aug 2026 15:14:55 +0100
Subject: [PATCH 06/12] doc: Tidy release notes
---
docs/release-notes/.FSharp.Core/11.0.100.md | 9 ++++-----
src/FSharp.Core/async.fsi | 2 +-
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index e18e415092a..ba44cd453d8 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -8,8 +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.Await`, which mirrors `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes ValueTask support, and a SRTP-based overload accepting any task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785))
-* Add `Async.StartTaskImmediate`, which propagates the ambient `Async.CancellationToken` to a task factory, then awaits 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))
+* Add `Async.StartTaskImmediate`, which propagates the ambient `Async.CancellationToken` to a task factory, then awaits 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.fsi b/src/FSharp.Core/async.fsi
index 123c0336d34..a8f8f307ad6 100644
--- a/src/FSharp.Core/async.fsi
+++ b/src/FSharp.Core/async.fsi
@@ -1439,7 +1439,7 @@ namespace Microsoft.FSharp.Control
/// async {
/// let! html = fetchConfigured "https://example.com"
/// printfn $"Downloaded {html.Length} chars"
- /// } |> Async.RunSynchronously
+ /// } |> Async.RunSynchronouslyImmediate
///
///
[]
From 0540c495a171788afb6c63f234e7e6ac0cfb08e2 Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Thu, 13 Aug 2026 17:17:07 +0100
Subject: [PATCH 07/12] review: tidy tests
---
.../Microsoft.FSharp.Control/AsyncType.fs | 59 ++++++++-----------
1 file changed, 23 insertions(+), 36 deletions(-)
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 cafc916cb54..9b33aaedb77 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
@@ -653,36 +653,33 @@ type AsyncType() =
let ok = Async.RunSynchronously a
Assert.True ok
- (* StartTaskImmediate(Task/Task<'T>) coverage *)
+ (* StartTaskImmediate(Task/Task<'T>) *)
[]
member _.``StartTaskImmediate(Task<'T>) flows result``() =
let a = async {
- let! v = Async.StartTaskImmediate(fun _ct -> Task.FromResult(42))
+ let! v = Async.StartTaskImmediate(fun _ct -> Task.result 42)
return v = 42
}
- Assert.True(Async.RunSynchronously a)
+ Assert.True(Async.RunSynchronouslyImmediate a)
[]
member _.``StartTaskImmediate(Task) happy path``() =
let mutable called = false
let a = async {
- do! Async.StartTaskImmediate(fun _ct -> Task.Run(fun () -> called <- true))
- return true
+ do! Async.StartTaskImmediate(fun _ct -> task { called <- true })
}
- let ok = Async.RunSynchronously a
- Assert.True(called && ok)
+ 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 ->
- capturedCt <- ct
- Task.CompletedTask)
+ do! Async.StartTaskImmediate(fun ct -> task { capturedCt <- ct })
}
- Async.RunSynchronously(a, cancellationToken = cts.Token)
+ Async.RunSynchronouslyImmediate(a, cancellationToken = cts.Token)
Assert.Equal(cts.Token, capturedCt)
[]
@@ -694,8 +691,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 _.``StartTaskImmediate(Task) exception unwraps``() =
@@ -706,8 +702,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 _.``StartTaskImmediate(Task<'T>) cancellation raises TaskCanceledException``() =
@@ -718,8 +713,7 @@ type AsyncType() =
return false
with :? TaskCanceledException -> return true
}
- let ok = Async.RunSynchronously a
- Assert.True ok
+ Assert.True(Async.RunSynchronouslyImmediate a)
#if !NETFRAMEWORK
(* Await(ValueTask and ValueTask<'T>) overloads coverage of mainline behaviors *)
@@ -730,8 +724,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``() =
@@ -739,8 +732,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``() =
@@ -752,8 +744,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``() =
@@ -764,10 +755,9 @@ 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>) coverage *)
+ (* StartTaskImmediate(ValueTask/ValueTask<'T>) *)
[]
member _.``StartTaskImmediate(ValueTask<'T>) flows result``() =
@@ -775,7 +765,7 @@ type AsyncType() =
let! v = Async.StartTaskImmediate(fun _ct -> ValueTask(42))
return v = 42
}
- Assert.True(Async.RunSynchronously a)
+ Assert.True(Async.RunSynchronouslyImmediate a)
[]
member _.``StartTaskImmediate(ValueTask) happy path``() =
@@ -783,7 +773,7 @@ type AsyncType() =
do! Async.StartTaskImmediate(fun _ct -> ValueTask())
return true
}
- Assert.True(Async.RunSynchronously a)
+ Assert.True(Async.RunSynchronouslyImmediate a)
[]
member _.``StartTaskImmediate(ValueTask<'T>) exception unwraps``() =
@@ -795,7 +785,7 @@ type AsyncType() =
return false
with :? ArgumentException as ae -> return ae.Message = "original"
}
- Assert.True(Async.RunSynchronously a)
+ Assert.True(Async.RunSynchronouslyImmediate a)
[]
member _.``StartTaskImmediate(ValueTask) exception unwraps``() =
@@ -807,10 +797,9 @@ type AsyncType() =
return false
with :? ArgumentException as ae -> return ae.Message = "original"
}
- Assert.True(Async.RunSynchronously a)
+ Assert.True(Async.RunSynchronouslyImmediate a)
#endif
-[]
module AsyncTaskLikeAwaitTests =
// Minimal custom task-like type wrapping Task<'T>
@@ -914,7 +903,6 @@ module AsyncTaskLikeAwaitTests =
|> Async.RunSynchronously
Assert.Equal(42, result)
-[]
module AsyncStartTaskImmediateTaskLikeTests =
[]
@@ -927,7 +915,7 @@ module AsyncStartTaskImmediateTaskLikeTests =
do! Async.StartTaskImmediate(fun _ -> Task.Yield())
after <- true
}
- |> Async.RunSynchronously
+ |> Async.RunSynchronouslyImmediate
Assert.True(before && after)
@@ -938,7 +926,7 @@ module AsyncStartTaskImmediateTaskLikeTests =
async {
return! Async.StartTaskImmediate(fun _ -> Task.FromResult(42).ConfigureAwait(false))
}
- |> Async.RunSynchronously
+ |> Async.RunSynchronouslyImmediate
Assert.Equal(42, result)
@@ -953,11 +941,10 @@ module AsyncStartTaskImmediateTaskLikeTests =
capturedCt <- ct
Task.CompletedTask.ConfigureAwait(false))
}
- Async.RunSynchronously(a, cancellationToken = cts.Token)
+ Async.RunSynchronouslyImmediate(a, cancellationToken = cts.Token)
Assert.Equal(cts.Token, capturedCt)
-[]
module AsyncAwaitStackTraceTests =
open System.Runtime.CompilerServices
From f777b5d5f2aa4daff11e249e580ffabbf46f687d Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Thu, 13 Aug 2026 17:38:42 +0100
Subject: [PATCH 08/12] doc: Add crossreference to StartTaskImmediate from
Await/AwaitTask
---
src/FSharp.Core/async.fsi | 33 ++++++++++++++++++++++++++++-----
1 file changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi
index a8f8f307ad6..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
///
@@ -1359,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.
From 69f2d3ba5eceefe41ef7383bf7c7e7d55ee250be Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Thu, 13 Aug 2026 18:10:14 +0100
Subject: [PATCH 09/12] review: clarify comments
---
.../FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
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 9b33aaedb77..d0452d1c2b9 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() =
From 267be0c2a1b36d2fc8de127656cb4767ea3e2343 Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Thu, 13 Aug 2026 18:16:58 +0100
Subject: [PATCH 10/12] tidy: Linting/style
---
.../Microsoft.FSharp.Control/AsyncType.fs | 45 ++++++++++---------
1 file changed, 23 insertions(+), 22 deletions(-)
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 d0452d1c2b9..b8e4df54785 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
@@ -83,7 +83,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,
@@ -105,7 +105,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)
@@ -130,7 +130,7 @@ 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 -> ()
@@ -139,7 +139,7 @@ type AsyncType() =
[]
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,
@@ -157,7 +157,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)
[]
@@ -167,7 +167,7 @@ type AsyncType() =
let doSpinloop () = while spinloop do ()
let a = async {
asyncStarted.Set()
- cts.CancelAfter (100)
+ cts.CancelAfter(100)
doSpinloop()
}
@@ -180,7 +180,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
@@ -199,8 +199,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)
@@ -208,7 +208,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 ()
@@ -252,7 +252,7 @@ type AsyncType() =
t.Wait()
with
e -> exceptionThrown <- true
- Assert.True (t.IsFaulted)
+ Assert.True(t.IsFaulted)
Assert.True(exceptionThrown)
[]
@@ -269,7 +269,7 @@ type AsyncType() =
try
waitForCompletion t
with e -> exceptionThrown <- true
- Assert.True (exceptionThrown)
+ Assert.True(exceptionThrown)
Assert.True(t.IsCanceled)
[]
@@ -292,7 +292,7 @@ type AsyncType() =
try
t.Wait()
with e -> exceptionThrown <- true
- Assert.True (exceptionThrown)
+ Assert.True(exceptionThrown)
Assert.True(t.IsCanceled)
Assert.True(cancelled)
@@ -302,7 +302,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)
[]
@@ -311,7 +311,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)
@@ -326,7 +326,7 @@ type AsyncType() =
t.Wait()
with
e -> exceptionThrown <- true
- Assert.True (t.IsFaulted)
+ Assert.True(t.IsFaulted)
Assert.True(exceptionThrown)
[]
@@ -341,7 +341,7 @@ type AsyncType() =
try
t.Wait()
with e -> exceptionThrown <- true
- Assert.True (exceptionThrown)
+ Assert.True(exceptionThrown)
Assert.True(t.IsCanceled)
[]
@@ -364,7 +364,7 @@ type AsyncType() =
try
t.Wait()
with e -> exceptionThrown <- true
- Assert.True (exceptionThrown)
+ Assert.True(exceptionThrown)
Assert.True(t.IsCanceled)
Assert.True(cancelled)
@@ -500,7 +500,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()
@@ -639,7 +639,7 @@ type AsyncType() =
[]
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
@@ -648,7 +648,7 @@ type AsyncType() =
[]
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
@@ -1029,5 +1029,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
From a8468e056061355086bdd209d87ab15a814d00e1 Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Thu, 13 Aug 2026 18:46:13 +0100
Subject: [PATCH 11/12] cleanup: Use/cover RunSynchronouslyImmediate
---
.../Microsoft.FSharp.Control/AsyncType.fs | 86 ++++++++-----------
1 file changed, 36 insertions(+), 50 deletions(-)
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 b8e4df54785..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
@@ -55,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.
@@ -92,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)
)
@@ -134,7 +135,7 @@ type AsyncType() =
failwith "Expected ArgumentOutOfRangeException"
with
| :? ArgumentOutOfRangeException -> ()
- } |> Async.RunSynchronously
+ } |> Async.RunSynchronouslyImmediate
[]
member _.AsyncSleepInfinitely() =
@@ -147,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)
)
@@ -233,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 () =
@@ -376,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) =
@@ -389,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() =
@@ -400,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) =
@@ -413,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) =
@@ -424,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) =
@@ -458,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)
[]
@@ -470,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) =
@@ -520,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
@@ -562,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) =
@@ -575,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
@@ -592,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.
[]
@@ -605,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 ...
[]
@@ -618,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.
[]
@@ -631,8 +621,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)
(* Await(Task/Task<'T>) overloads happy path *)
@@ -642,8 +631,7 @@ type AsyncType() =
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``() =
@@ -651,8 +639,7 @@ type AsyncType() =
do! Async.Await(Task.CompletedTask)
return true
}
- let ok = Async.RunSynchronously a
- Assert.True ok
+ Assert.True(Async.RunSynchronouslyImmediate a)
(* StartTaskImmediate(Task/Task<'T>) *)
@@ -813,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``() =
@@ -878,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``() =
@@ -890,7 +876,7 @@ module AsyncTaskLikeAwaitTests =
do! Async.Await(Task.Yield())
after <- true
}
- |> Async.RunSynchronously
+ |> Async.RunSynchronouslyImmediate
Assert.True(before && after)
[]
@@ -901,7 +887,7 @@ module AsyncTaskLikeAwaitTests =
let! v = Async.Await(Task.FromResult(42).ConfigureAwait(false))
return v
}
- |> Async.RunSynchronously
+ |> Async.RunSynchronouslyImmediate
Assert.Equal(42, result)
module AsyncStartTaskImmediateTaskLikeTests =
@@ -970,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
From 8f9554f71d64a6a0a2f1bc8353eafc56de44c92a Mon Sep 17 00:00:00 2001
From: Ruben Bartelink
Date: Fri, 14 Aug 2026 12:11:24 +0100
Subject: [PATCH 12/12] doc(release notes): Polish
---
docs/release-notes/.FSharp.Core/11.0.100.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index ba44cd453d8..2cf66edaa10 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -8,7 +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))
-* Add `Async.StartTaskImmediate`, which propagates the ambient `Async.CancellationToken` to a task factory, then awaits 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.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))