Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

* Ship `FSharp.Core` with an additional `net10.0` target framework (next to `netstandard2.0` and `netstandard2.1`). The `net`-TFM assembly is public-surface-identical to the `netstandard2.1` one; the target version is a pinned, deliberately advanced knob. ([PR #20229](https://github.com/dotnet/fsharp/pull/20229))
* 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))
24 changes: 24 additions & 0 deletions src/FSharp.Core/async.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<unit> =
CreateBindAsync Async.CancellationToken (createTask >> Async.Await)

#if NETSTANDARD2_1 || NET
static member StartTaskImmediate(createTask: CancellationToken -> ValueTask<'T>) : Async<'T> =
CreateBindAsync Async.CancellationToken (createTask >> Async.Await)

static member StartTaskImmediate(createTask: CancellationToken -> ValueTask) : Async<unit> =
CreateBindAsync Async.CancellationToken (createTask >> Async.Await)
#endif

module AsyncTaskLikeExtensions =

type Async with
Expand Down Expand Up @@ -2296,6 +2310,16 @@ module AsyncTaskLikeExtensions =
with e ->
econt e))

[<NoEagerConstraintApplication>]
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
Expand Down
153 changes: 148 additions & 5 deletions src/FSharp.Core/async.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -782,12 +782,16 @@ namespace Microsoft.FSharp.Control
/// its result. Note exceptions are wrapped in <see cref="T:System.AggregateException"/>; for new
/// code, prefer <c>Async.Await</c>, which surfaces single exceptions directly.</summary>
/// <param name="task">The task to await.</param>
/// <remarks>If the task is canceled then <see cref="T:System.Threading.Tasks.TaskCanceledException"/> is raised. Note
/// <remarks>
/// <p>If the task is canceled then <see cref="T:System.Threading.Tasks.TaskCanceledException"/> 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 <c>let! ct = Async.CancellationToken</c>, and catch
/// any <see cref="T:System.Threading.Tasks.TaskCanceledException"/> at the point where the
/// overall async is started.
/// overall async is started.</p>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// </remarks>
/// <category index="2">Awaiting Results</category>
/// <example id="awaittask-1">
Expand All @@ -813,12 +817,15 @@ namespace Microsoft.FSharp.Control
/// Note exceptions are wrapped in <see cref="T:System.AggregateException"/>; for new
/// code, prefer <c>Async.Await</c>, which surfaces single exceptions directly.</summary>
/// <param name="task">The task to await.</param>
/// <remarks>If the task is canceled then <see cref="T:System.Threading.Tasks.TaskCanceledException"/> is raised. Note
/// <remarks><p>If the task is canceled then <see cref="T:System.Threading.Tasks.TaskCanceledException"/> 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 <c>let! ct = Async.CancellationToken</c>, and catch
/// any <see cref="T:System.Threading.Tasks.TaskCanceledException"/> at the point where the
/// overall async is started.
/// overall async is started.</p>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// </remarks>
/// <category index="2">Awaiting Results</category>
/// <example id="awaittask-2">
Expand Down Expand Up @@ -856,6 +863,9 @@ namespace Microsoft.FSharp.Control
/// typically tasks should be wired to the ambient cancellation token obtained via
/// <c>let! ct = Async.CancellationToken</c>, catching <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
/// where the overall async is started.</p>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// </remarks>
///
/// <category index="2">Awaiting Results</category>
Expand Down Expand Up @@ -893,6 +903,9 @@ namespace Microsoft.FSharp.Control
/// typically tasks should be wired to the ambient cancellation token obtained via
/// <c>let! ct = Async.CancellationToken</c>, catching <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
/// where the overall async is started.</p>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// </remarks>
/// <category index="2">Awaiting Results</category>
/// <example id="await-task-2">
Expand Down Expand Up @@ -929,6 +942,9 @@ namespace Microsoft.FSharp.Control
/// typically tasks should be wired to the ambient cancellation token obtained via
/// <c>let! ct = Async.CancellationToken</c>, catching <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
/// where the overall async is started.</p>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// </remarks>
/// <category index="2">Awaiting Results</category>
/// <example id="await-valuetask-1">
Expand Down Expand Up @@ -963,6 +979,9 @@ namespace Microsoft.FSharp.Control
/// typically tasks should be wired to the ambient cancellation token obtained via
/// <c>let! ct = Async.CancellationToken</c>, catching <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
/// where the overall async is started.</p>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// </remarks>
/// <category index="2">Awaiting Results</category>
/// <example id="await-valuetask-2">
Expand All @@ -983,6 +1002,79 @@ namespace Microsoft.FSharp.Control
static member Await: task: ValueTask -> Async<unit>
#endif

/// <summary>Creates an asynchronous computation that passes the ambient <c>Async.CancellationToken</c> to
/// <c>createTask</c>, and then awaits the resulting task, returning its result.</summary>
///
/// <param name="createTask">A function that accepts a <c>CancellationToken</c> and returns a <c>Task&lt;'T&gt;</c>.</param>
///
/// <remarks>The cancellation token of the enclosing async computation is automatically passed to
/// <c>createTask</c>, propagating cancellation naturally to the task without requiring manual token capture.
///
/// The resulting task is awaited using <see cref="M:Microsoft.FSharp.Control.FSharpAsync.Await``1(System.Threading.Tasks.Task{``0})"/>;
/// exception unwrapping and cancellation handling are as per that overload.
/// </remarks>
/// <category index="0">Starting Async Computations</category>
/// <example id="startTaskImmediate-taskt-1">
/// <code lang="fsharp">
/// async {
/// let! text = Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync("file.txt", ct))
/// printfn "Content: %s" text
/// }
/// </code>
/// </example>
static member StartTaskImmediate: createTask: (CancellationToken -> Task<'T>) -> Async<'T>

/// <summary>Creates an asynchronous computation that passes the ambient <c>Async.CancellationToken</c> to
/// <c>createTask</c>, and then awaits the resulting task.</summary>
///
/// <param name="createTask">A function that accepts a <c>CancellationToken</c> and returns a <c>Task</c>.</param>
///
/// <remarks>The cancellation token of the enclosing async computation is automatically passed to
/// <c>createTask</c>, propagating cancellation naturally to the task without requiring manual token capture.
///
/// The resulting task is awaited using <see cref="M:Microsoft.FSharp.Control.FSharpAsync.Await(System.Threading.Tasks.Task)"/>;
/// exception unwrapping and cancellation handling are as per that overload.
/// </remarks>
/// <category index="0">Starting Async Computations</category>
/// <example id="startTaskImmediate-task-1">
/// <code lang="fsharp">
/// async {
/// do! Async.StartTaskImmediate(fun ct -> File.WriteAllTextAsync("file.txt", "hello", ct))
/// }
/// </code>
/// </example>
static member StartTaskImmediate: createTask: (CancellationToken -> Task) -> Async<unit>

#if NETSTANDARD2_1 || NET
/// <summary>Creates an asynchronous computation that passes the ambient <c>Async.CancellationToken</c> to
/// <c>createTask</c>, and then awaits the resulting <c>ValueTask</c>, returning its result.</summary>
///
/// <param name="createTask">A function that accepts a <c>CancellationToken</c> and returns a <c>ValueTask&lt;'T&gt;</c>.</param>
///
/// <remarks>The cancellation token of the enclosing async computation is automatically passed to
/// <c>createTask</c>, propagating cancellation naturally to the task without requiring manual token capture.
///
/// The resulting task is awaited using <see cref="M:Microsoft.FSharp.Control.FSharpAsync.Await``1(System.Threading.Tasks.ValueTask{``0})"/>;
/// exception unwrapping and cancellation handling are as per that overload.
/// </remarks>
/// <category index="0">Starting Async Computations</category>
static member StartTaskImmediate: createTask: (CancellationToken -> ValueTask<'T>) -> Async<'T>

/// <summary>Creates an asynchronous computation that passes the ambient <c>Async.CancellationToken</c> to
/// <c>createTask</c>, and then awaits the resulting <c>ValueTask</c>.</summary>
///
/// <param name="createTask">A function that accepts a <c>CancellationToken</c> and returns a <c>ValueTask</c>.</param>
///
/// <remarks>The cancellation token of the enclosing async computation is automatically passed to
/// <c>createTask</c>, propagating cancellation naturally to the task without requiring manual token capture.
///
/// The resulting task is awaited using <see cref="M:Microsoft.FSharp.Control.FSharpAsync.Await(System.Threading.Tasks.ValueTask)"/>;
/// exception unwrapping and cancellation handling are as per that overload.
/// </remarks>
/// <category index="0">Starting Async Computations</category>
static member StartTaskImmediate: createTask: (CancellationToken -> ValueTask) -> Async<unit>

#endif
/// <summary>
/// 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
Expand Down Expand Up @@ -1286,7 +1378,11 @@ namespace Microsoft.FSharp.Control
/// <summary>Creates an asynchronous computation that will wait for the given task-like value to complete and return
/// its result.</summary>
/// <param name="task">The task-like value to await.</param>
/// <remarks><p>The value must satisfy the GetAwaiter pattern: it must have a <c>GetAwaiter()</c> method
/// <remarks>
/// <p>For the common case where you are running a Task within an Asynchronous Computation,
/// see <c>StartTaskImmediate</c>, which surfaces the ambient <c>CancellationToken</c>
/// so that it can be passed to the Task being started.</p>
/// <p>The value must satisfy the GetAwaiter pattern: it must have a <c>GetAwaiter()</c> method
/// returning an awaiter implementing <see cref="T:System.Runtime.CompilerServices.ICriticalNotifyCompletion"/>
/// with <c>IsCompleted</c> and <c>GetResult()</c> members.</p>
/// <p>Exceptions thrown by <c>GetResult()</c> are propagated directly.</p>
Expand Down Expand Up @@ -1330,6 +1426,53 @@ namespace Microsoft.FSharp.Control
and ^Awaiter: (member get_IsCompleted: unit -> bool)
and ^Awaiter: (member GetResult: unit -> 'T)

/// <summary>Creates an asynchronous computation that passes the ambient <c>Async.CancellationToken</c> to
/// <c>createTask</c>, and then awaits the resulting task-like value.</summary>
///
/// <param name="createTask">A function that accepts a <c>CancellationToken</c> and returns a task-like value
/// satisfying the GetAwaiter pattern.</param>
///
/// <remarks>The value returned by <c>createTask</c> must satisfy the GetAwaiter pattern: it must have a
/// <c>GetAwaiter()</c> method returning an awaiter implementing
/// <see cref="T:System.Runtime.CompilerServices.ICriticalNotifyCompletion"/>
/// with <c>IsCompleted</c> and <c>GetResult()</c> members.
///
/// This overload uses statically resolved type parameters (SRTP) so it can accept factories returning
/// any task-like type, including <c>YieldAwaitable</c> (from <c>Task.Yield()</c>) and
/// <c>ConfiguredTaskAwaitable</c> (from <c>task.ConfigureAwait(false)</c>).
/// The specific overloads for <see cref="T:System.Threading.Tasks.Task`1"/>, <see cref="T:System.Threading.Tasks.Task"/>,
/// <see cref="T:System.Threading.Tasks.ValueTask`1"/> and <see cref="T:System.Threading.Tasks.ValueTask"/>
/// are preferred when the factory return type is known.
/// </remarks>
/// <category index="0">Starting Async Computations</category>
/// <example id="startTaskImmediate-tasklike-1">
/// <code lang="fsharp">
/// // Straightforward: factory returns Task&lt;string&gt;, which is handled by
/// // the specific Task&lt;'T&gt; overload of StartTaskImmediate (not this one).
/// let fetchPlain (url: string) =
/// Async.StartTaskImmediate(fun ct ->
/// httpClient.GetStringAsync(url, ct)) // returns Task&lt;string&gt;
///
/// // Adding ConfigureAwait(false) to the mix yields a ConfiguredTaskAwaitable&lt;string&gt;,
/// // 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
/// </code>
/// </example>
[<NoEagerConstraintApplication>]
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)

/// <summary>The F# compiler emits references to this type to implement F# async expressions.</summary>
///
/// <category index="5">Async Internals</category>
Expand Down
Loading
Loading