Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0677d34
feat(Async): Add exception-unwrapping Await
bartelink May 21, 2026
9240ff8
Polish xmldoc
bartelink May 22, 2026
97c31eb
Fix test expectation
bartelink May 22, 2026
3ae385d
hack to silence test fail
bartelink May 22, 2026
76f5bad
Apply majocha correction
bartelink May 22, 2026
cd66660
Remove layer of indirection
bartelink May 22, 2026
ae14fcb
add support for TaskLike
bartelink May 23, 2026
00b5b23
Fix test expectation
bartelink May 23, 2026
76995f1
Add stacktrace validation
bartelink May 24, 2026
22022a0
Add specific TaskLike cases
bartelink May 25, 2026
3bf8d2f
Apply even more feedback goodness
bartelink May 25, 2026
04446a9
Merge remote-tracking branch 'upstream/main' into async-await
bartelink Jul 13, 2026
f62dc2d
fix: Correct #if condition in tests
bartelink Jul 13, 2026
1e853ee
fix: Replace UnsafeOnCompleted with OnCompleted
bartelink Jul 13, 2026
5c12141
doc: Exception semantics details per review
bartelink Jul 13, 2026
e1d0708
docs: reinstate proviso about flowing CT
bartelink Jul 13, 2026
2e532e0
doc: Remove broken reference for netstandard2.0
bartelink Jul 13, 2026
197ff01
review: add comment re role of Await Tasklike
bartelink Jul 13, 2026
95595bd
docs: add missing reference
bartelink Jul 13, 2026
334a40f
test: Regression for UnsafeOnCompletion
bartelink Jul 13, 2026
e9b7c32
Merge remote-tracking branch 'upstream/main' into async-await
bartelink Jul 25, 2026
cb5c378
doc: Polish CL
bartelink Jul 25, 2026
928ca92
fix: Make test compile on ns2.0/net471
bartelink Jul 25, 2026
7d6ee43
fix: Correct #if guard using wrong symbol
bartelink Jul 25, 2026
5e37657
fix: Update tooltip expected to include Await
bartelink Jul 26, 2026
a9dac92
fix: Adjust ifdef'd xmldoc to pass validation
bartelink Jul 26, 2026
13d205f
fix: Update extractXmlDocBlocks to handle cond compilation
bartelink Jul 26, 2026
f52f2ca
fix: Correct error message line numbering
bartelink Jul 26, 2026
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 @@ -4,3 +4,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))
* Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869))

### 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))
123 changes: 99 additions & 24 deletions src/FSharp.Core/async.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ open System.Runtime.ExceptionServices
open System.Threading
open System.Threading.Tasks
open Microsoft.FSharp.Core
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators
open Microsoft.FSharp.Control
open Microsoft.FSharp.Collections
Expand Down Expand Up @@ -1210,16 +1211,30 @@ module AsyncPrimitives =

task

// Used by Async.Await path to elide egregious AggregateException wrapping
[<DebuggerHidden>]
let UnwrapExn (exn: AggregateException) =
if exn.InnerExceptions.Count = 1 then
exn.InnerExceptions[0]
else
exn

// Call the appropriate continuation on completion of a task
[<DebuggerHidden>]
let OnTaskCompleted (completedTask: Task<'T>) (ctxt: AsyncActivation<'T>) =
let OnTaskCompleted unwrap (completedTask: Task<'T>) (ctxt: AsyncActivation<'T>) =
assert completedTask.IsCompleted

if completedTask.IsCanceled then
let edi = ExceptionDispatchInfo.Capture(TaskCanceledException completedTask)
ctxt.econt edi
elif completedTask.IsFaulted then
let edi = ExceptionDispatchInfo.RestoreOrCapture completedTask.Exception
let e =
if unwrap then
UnwrapExn completedTask.Exception
else
completedTask.Exception

let edi = ExceptionDispatchInfo.RestoreOrCapture e
ctxt.econt edi
else
ctxt.cont completedTask.Result
Expand All @@ -1229,14 +1244,20 @@ module AsyncPrimitives =
// the overall async (they may be governed by different cancellation tokens, or
// the task may not have a cancellation token at all).
[<DebuggerHidden>]
let OnUnitTaskCompleted (completedTask: Task) (ctxt: AsyncActivation<unit>) =
let OnUnitTaskCompleted unwrap (completedTask: Task) (ctxt: AsyncActivation<unit>) =
assert completedTask.IsCompleted

if completedTask.IsCanceled then
let edi = ExceptionDispatchInfo.Capture(TaskCanceledException(completedTask))
ctxt.econt edi
elif completedTask.IsFaulted then
let edi = ExceptionDispatchInfo.RestoreOrCapture completedTask.Exception
let e =
if unwrap then
UnwrapExn completedTask.Exception
else
completedTask.Exception

let edi = ExceptionDispatchInfo.RestoreOrCapture e
ctxt.econt edi
else
ctxt.cont ()
Expand All @@ -1246,10 +1267,10 @@ module AsyncPrimitives =
// completing the task. This will install a new trampoline on that thread and continue the
// execution of the async there.
[<DebuggerHidden>]
let AttachContinuationToTask (task: Task<'T>) (ctxt: AsyncActivation<'T>) =
let AttachContinuationToTask unwrap (task: Task<'T>) (ctxt: AsyncActivation<'T>) =
task.ContinueWith(
Action<Task<'T>>(fun completedTask ->
ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnTaskCompleted completedTask ctxt)
ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnTaskCompleted unwrap completedTask ctxt)
|> unfake),
TaskContinuationOptions.ExecuteSynchronously
)
Expand All @@ -1261,16 +1282,36 @@ module AsyncPrimitives =
// completing the task. This will install a new trampoline on that thread and continue the
// execution of the async there.
[<DebuggerHidden>]
let AttachContinuationToUnitTask (task: Task) (ctxt: AsyncActivation<unit>) =
let AttachContinuationToUnitTask unwrap (task: Task) (ctxt: AsyncActivation<unit>) =
task.ContinueWith(
Action<Task>(fun completedTask ->
ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnUnitTaskCompleted completedTask ctxt)
ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnUnitTaskCompleted unwrap completedTask ctxt)
|> unfake),
TaskContinuationOptions.ExecuteSynchronously
)
|> ignore
|> fake

let AwaitTask unwrap (task: Task<'T>) =
MakeAsyncWithCancelCheck(fun ctxt ->
if task.IsCompleted then
// Run synchronously without installing new trampoline
OnTaskCompleted unwrap task ctxt
else
// Continue asynchronously, via syncContext if necessary, installing new trampoline
let ctxt = DelimitSyncContext ctxt
ctxt.ProtectCode(fun () -> AttachContinuationToTask unwrap task ctxt))

let AwaitUnitTask unwrap (task: Task) =
MakeAsyncWithCancelCheck(fun ctxt ->
if task.IsCompleted then
// Continue synchronously without installing new trampoline
OnUnitTaskCompleted unwrap task ctxt
else
// Continue asynchronously, via syncContext if necessary, installing new trampoline
let ctxt = DelimitSyncContext ctxt
ctxt.ProtectCode(fun () -> AttachContinuationToUnitTask unwrap task ctxt))

/// Removes a registration places on a cancellation token
let DisposeCancellationRegistration (registration: byref<CancellationTokenRegistration option>) =
match registration with
Expand Down Expand Up @@ -2203,24 +2244,58 @@ type Async =
CreateWhenCancelledAsync compensation computation

static member AwaitTask(task: Task<'T>) : Async<'T> =
MakeAsyncWithCancelCheck(fun ctxt ->
if task.IsCompleted then
// Run synchronously without installing new trampoline
OnTaskCompleted task ctxt
else
// Continue asynchronously, via syncContext if necessary, installing new trampoline
let ctxt = DelimitSyncContext ctxt
ctxt.ProtectCode(fun () -> AttachContinuationToTask task ctxt))
AwaitTask false task

static member AwaitTask(task: Task) : Async<unit> =
MakeAsyncWithCancelCheck(fun ctxt ->
if task.IsCompleted then
// Continue synchronously without installing new trampoline
OnUnitTaskCompleted task ctxt
else
// Continue asynchronously, via syncContext if necessary, installing new trampoline
let ctxt = DelimitSyncContext ctxt
ctxt.ProtectCode(fun () -> AttachContinuationToUnitTask task ctxt))
AwaitUnitTask false task

static member Await(task: Task<'T>) : Async<'T> =
AwaitTask true task

static member Await(task: Task) : Async<unit> =
AwaitUnitTask true task

#if NETSTANDARD2_1
static member Await(task: ValueTask<'T>) : Async<'T> =
if task.IsCompletedSuccessfully then
CreateReturnAsync(task.GetAwaiter().GetResult())
else
AwaitTask true (task.AsTask())

static member Await(task: ValueTask) : Async<unit> =
if task.IsCompletedSuccessfully then
CreateReturnAsync(task.GetAwaiter().GetResult())
else
AwaitUnitTask true (task.AsTask())
#endif

module AsyncTaskLikeExtensions =

type Async with

[<NoEagerConstraintApplication>]
static member inline Await< ^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)>
(task: ^TaskLike)
: Async<'T> =
Async.FromContinuations(fun (cont, econt, _ccont) ->
let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) task)

if (^Awaiter: (member get_IsCompleted: unit -> bool) awaiter) then
try
cont ((^Awaiter: (member GetResult: unit -> 'T) awaiter))
Comment thread
T-Gro marked this conversation as resolved.
with e ->
econt e
else
(awaiter :> ICriticalNotifyCompletion)
.OnCompleted(fun () ->
try
cont ((^Awaiter: (member GetResult: unit -> 'T) awaiter))
with e ->
econt e))

module CommonExtensions =

Expand Down
Loading
Loading