Skip to content

feat(Async): Add exception-unwrapping Await#19785

Draft
bartelink wants to merge 1 commit into
dotnet:mainfrom
bartelink:async-await
Draft

feat(Async): Add exception-unwrapping Await#19785
bartelink wants to merge 1 commit into
dotnet:mainfrom
bartelink:async-await

Conversation

@bartelink
Copy link
Copy Markdown

@bartelink bartelink commented May 21, 2026

Implements Async.Await for Task, Task<'T>, ValueTask and ValueTask<'T> (aka the community AwaitTaskCorrect implemention)

Key differentiation from Async.AwaitTask is that AggregateExceptions are unwrapped such that a try ... with <ExceptionType> -> will type-match correctly.

Key distinction from the canonical implementation (which derives from https://www.fssnip.net/7Rc/title/AsyncAwaitTaskCorrect) is that the implementation is intended to have 1:1 matching of all stacktrace preservation properties borne by AwaitTask (and continue to track that over time).

NOTE one key implementation decision is that this PR does NOT attempt to resolve #2127 so:

  1. if the computation workflow's CT is cancelled at the point where Await is invoked, normal cancellation semantics as per AwaitTask apply:
    • exception continuation is passed an OperationCanceledException
    • the Task in question's Result will go unobserved
  2. if a cancellation of the computation workflow via it's ambient cancellation token is triggered during the course of the Await, it will (like AwaitTask):
    • NOT abort and abandon the observation Task
    • instead, it will wait [as a C# await would] until such time as the Task completes (either successfully, with a fault, or via cancellation)

Checklist

  • Test cases added
  • Release notes entry updated
  • Add documentation cross-links and/or deprecation notices flagging that:
    • Await can technically still propagate an AggregateException
    • AwaitTask will always yield an AggregateException
    • AwaitTask should be considered deprecated (and may in future be subject to Obsoleteion warnings?)
    • Await should be the used in preference for new code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/FSharp.Core docs/release-notes/.FSharp.Core/11.0.100.md

@bartelink bartelink force-pushed the async-await branch 6 times, most recently from c1919eb to b2b2b6a Compare May 21, 2026 14:35
@bartelink
Copy link
Copy Markdown
Author

bartelink commented May 21, 2026

@T-Gro if you and/or others can give this a quick scan please, I'd like to confirm:

  1. nobody else has work in flight (yes, a bit late for that!)
  2. the rough approach is viable
  3. the key impl decision is reasonable:
    • no attempt to get too clever and have the API react to Async cancellation by abandoning waits, i.e. if you have code that does not propagate Async.CancellationToken to a Task start, Await will hang just as AwaitTask
      • BUT this is OK as abandoning in-flight tasks and/or the controlled disposal of associated resources/compute would not be strictly better
      • It aligns with e.g. how Async.Parallel waits for correct teardown/completion of all in-flight executions before yielding a result and/or completing the honoring of cancellation
    • Best practice recommendation will instead be to use Async.StartTaskImmediate, which will
  4. a rough indication of whether I should leave the xmldoc as is, or attempt to complete the rough tasks I've laid out in the checklist in the OP

TL;DR the overall proposition

  1. Await is just AwaitTask with unwrapping, zero other semantic change. => A better default to use where you'd otherwise use AwaitTask
  2. Usage of AwaitTask and Await are both smells - can you be sure all Tasks that have been started were correctly wired into the computation tree's CT?
  3. In general, usage of Async.Await should be replaced with/migrated to Async.StartTaskImmediate, which surfaces the problem
  4. if there was an analyzer flagging Async.Await/AwaitTask -> Async.StartTaskImmediate migration opportunities, a closely related one would be flagging cases of task { flows that use let! and/or do! bindings against Async<'T>) rather than using Task.startAsyncImmediate (which forces passing a CT to Async.StartImmediateAsTask

CC @TheAngryByrd @gusty @njlr who have provided useful feedback/review on stuff in this space in recent times

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Async.AwaitTask does not cancel on workflow cancellation

1 participant