Skip to content
Draft
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
4 changes: 4 additions & 0 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@

* Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672))
* Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667))

### Added

* Add `Async.RunSynchronouslyImmediate` which always starts on the current thread without a thread-pool hop, unlike `Async.RunSynchronously`. ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804))
20 changes: 13 additions & 7 deletions src/FSharp.Core/async.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,7 +1096,7 @@ module AsyncPrimitives =

/// Run the asynchronous workflow and wait for its result.
[<DebuggerHidden>]
let QueueAsyncAndWaitForResultSynchronously (token: CancellationToken) computation timeout =
let QueueAsyncAndWaitForResultSynchronously computation (token: CancellationToken) timeout =
let token, innerCTS =
// If timeout is provided, we govern the async by our own CTS, to cancel
// when execution times out. Otherwise, the user-supplied token governs the async.
Expand Down Expand Up @@ -1138,7 +1138,7 @@ module AsyncPrimitives =
res.Commit()

[<DebuggerHidden>]
let RunImmediate (cancellationToken: CancellationToken) computation =
let RunSynchronouslyImmediate computation (cancellationToken: CancellationToken) =
use resultCell = new ResultCell<AsyncResult<_>>()
let trampolineHolder = TrampolineHolder()

Expand All @@ -1158,11 +1158,11 @@ module AsyncPrimitives =
res.Commit()

[<DebuggerHidden>]
let RunSynchronously cancellationToken (computation: Async<'T>) timeout =
// Reuse the current ThreadPool thread if possible.
let RunSynchronouslyBackgroundThreadPool (computation: Async<'T>) cancellationToken timeout =
// Run inline only where it's guaranteed to be safe
match SynchronizationContext.Current, Thread.CurrentThread.IsThreadPoolThread, timeout with
| null, true, None -> RunImmediate cancellationToken computation
| _ -> QueueAsyncAndWaitForResultSynchronously cancellationToken computation timeout
| null, true, None -> RunSynchronouslyImmediate computation cancellationToken // clean stacktrace in case of exception
| _ -> QueueAsyncAndWaitForResultSynchronously computation cancellationToken timeout // NOTE no useful stack traces

[<DebuggerHidden>]
let Start cancellationToken (computation: Async<unit>) =
Expand Down Expand Up @@ -1511,7 +1511,13 @@ type Async =
| Some token when not token.CanBeCanceled -> timeout, token
| Some token -> None, token

RunSynchronously cancellationToken computation timeout
RunSynchronouslyBackgroundThreadPool computation cancellationToken timeout

static member RunSynchronouslyImmediate(computation: Async<'T>, ?cancellationToken: CancellationToken) =
let cancellationToken =
defaultArg cancellationToken defaultCancellationTokenSource.Token

RunSynchronouslyImmediate computation cancellationToken

static member Start(computation, ?cancellationToken) =
let cancellationToken =
Expand Down
62 changes: 51 additions & 11 deletions src/FSharp.Core/async.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,25 @@ namespace Microsoft.FSharp.Control

/// <summary>Runs the asynchronous computation and await its result.</summary>
///
/// <remarks>If an exception occurs in the asynchronous computation then an exception is re-raised by this
/// function.
/// <remarks>If an exception occurs in the asynchronous computation then it will be propagated to the caller.
///
/// If no cancellation token is provided then the default cancellation token is used.
///
/// The computation is started on the current thread if <see cref="P:System.Threading.SynchronizationContext.Current"/> is null,
/// <see cref="P:System.Threading.Thread.CurrentThread"/> has <see cref="P:System.Threading.Thread.IsThreadPoolThread"/>
/// of <c>true</c>, and no timeout is specified. Otherwise the computation is started by queueing a new work item in the thread pool,
/// and the current thread is blocked awaiting the completion of the computation.
/// <see cref="P:System.Threading.Thread.CurrentThread"/> has <see cref="P:System.Threading.Thread.IsThreadPoolThread"/>
/// of <c>true</c>, and no timeout is specified.<br/>
///
/// The timeout parameter is given in milliseconds. A value of -1 is equivalent to
/// Otherwise the computation is offloaded to the thread pool,
/// and the current thread is blocked awaiting the completion of the computation. Note in this case the stacktrace will be incomplete.<br/>
///
/// The timeout parameter is given in milliseconds. A value of -1 is equivalent to
/// <see cref="F:System.Threading.Timeout.Infinite"/>.
/// </remarks>
///
/// <param name="computation">The computation to run.</param>
/// <param name="timeout">The amount of time in milliseconds to wait for the result of the
/// computation before raising a <see cref="T:System.TimeoutException"/>. If no value is provided
/// for timeout then a default of -1 is used to correspond to <see cref="F:System.Threading.Timeout.Infinite"/>.</param>
/// <param name="timeout">The number of milliseconds to wait for the result of the
/// computation before raising a <see cref="T:System.TimeoutException"/>. If no value or -1 is provided
/// the timeout will be <see cref="F:System.Threading.Timeout.Infinite"/>.</param>
/// <param name="cancellationToken">The cancellation token to be associated with the computation.
/// If one is not supplied, the default cancellation token is used.</param>
///
Expand All @@ -87,10 +88,49 @@ namespace Microsoft.FSharp.Control
///
/// printfn "D"
/// </code>
/// Prints "A", "B" immediately, then "C", "D" in 1 second. result is set to 17.
/// Prints "A", "B" immediately, then "C", "D" in 1 second. Yields <c>result = 17</c>.
/// </example>
static member RunSynchronously : computation:Async<'T> * ?timeout : int * ?cancellationToken:CancellationToken-> 'T


/// <summary>Runs the asynchronous computation synchronously on the current thread, yielding its result.</summary>
///
/// <remarks>Unlike <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronously``1"/>, execution
/// always starts immediately on the calling thread even if
/// <see cref="P:System.Threading.SynchronizationContext.Current"/> being non-<c>null</c> or
/// <see cref="P:System.Threading.Thread.IsThreadPoolThread"/> being <c>false</c> would normally dictate offloading to a threadpool thread.
/// This key benefit is that this preserves call-stack context in the case of an exception.
///
/// If an exception occurs in the asynchronous computation then it will be propagated to the caller.
///
/// If no cancellation token is provided then the default cancellation token is used.
///
/// This overload does not support a timeout; see <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronously``1"/>
/// if a timeout is required.
/// </remarks>
///
/// <param name="computation">The computation to run.</param>
/// <param name="cancellationToken">The cancellation token to be associated with the computation.
/// If one is not supplied, the default cancellation token is used.</param>
/// <returns>The result of the computation.</returns>
/// <category index="0">Starting Async Computations</category>
///
/// <example id="run-synchronously-immediate-1">
/// <code lang="fsharp">
/// printfn "A"
///
/// let result = async {
/// printfn "B"
/// do! Async.Sleep(1000)
/// printfn "C"
/// 17
/// } |> Async.RunSynchronouslyImmediate
///
/// printfn "D"
/// </code>
/// Prints "A", "B" immediately (on the calling thread), then "C" (from a thread-pool thread), then "D" in 1 second (on the calling thread). Yields <c>result = 17</c>.
/// </example>
static member RunSynchronouslyImmediate : computation:Async<'T> * ?cancellationToken:CancellationToken -> 'T

/// <summary>Starts the asynchronous computation in the thread pool. Do not await its result.</summary>
///
/// <remarks>If no cancellation token is provided then the default cancellation token is used.</remarks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartAsTa
Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartImmediateAsTask[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg,System.AsyncCallback,System.Object],System.IAsyncResult],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,Microsoft.FSharp.Core.Unit]] AsBeginEnd[TArg,T](Microsoft.FSharp.Core.FSharpFunc`2[TArg,Microsoft.FSharp.Control.FSharpAsync`1[T]])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronously[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronouslyImmediate[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void CancelDefaultToken()
Microsoft.FSharp.Control.FSharpAsync: Void Start(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void StartImmediate(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartAsTa
Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartImmediateAsTask[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg,System.AsyncCallback,System.Object],System.IAsyncResult],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,Microsoft.FSharp.Core.Unit]] AsBeginEnd[TArg,T](Microsoft.FSharp.Core.FSharpFunc`2[TArg,Microsoft.FSharp.Control.FSharpAsync`1[T]])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronously[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronouslyImmediate[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void CancelDefaultToken()
Microsoft.FSharp.Control.FSharpAsync: Void Start(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void StartImmediate(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartAsTa
Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartImmediateAsTask[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg,System.AsyncCallback,System.Object],System.IAsyncResult],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,Microsoft.FSharp.Core.Unit]] AsBeginEnd[TArg,T](Microsoft.FSharp.Core.FSharpFunc`2[TArg,Microsoft.FSharp.Control.FSharpAsync`1[T]])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronously[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronouslyImmediate[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void CancelDefaultToken()
Microsoft.FSharp.Control.FSharpAsync: Void Start(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void StartImmediate(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartAsTa
Microsoft.FSharp.Control.FSharpAsync: System.Threading.Tasks.Task`1[T] StartImmediateAsTask[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: System.Tuple`3[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[TArg,System.AsyncCallback,System.Object],System.IAsyncResult],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T],Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,Microsoft.FSharp.Core.Unit]] AsBeginEnd[TArg,T](Microsoft.FSharp.Core.FSharpFunc`2[TArg,Microsoft.FSharp.Control.FSharpAsync`1[T]])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronously[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: T RunSynchronouslyImmediate[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void CancelDefaultToken()
Microsoft.FSharp.Control.FSharpAsync: Void Start(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Microsoft.FSharp.Control.FSharpAsync: Void StartImmediate(Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit], Microsoft.FSharp.Core.FSharpOption`1[System.Threading.CancellationToken])
Expand Down
Loading