Skip to content

Align Async.Await with fslang-suggestions #840 (raise TaskCanceledException on cancel) - #677

Open
T-Gro wants to merge 1 commit into
masterfrom
tgro-align-await
Open

Align Async.Await with fslang-suggestions #840 (raise TaskCanceledException on cancel)#677
T-Gro wants to merge 1 commit into
masterfrom
tgro-align-await

Conversation

@T-Gro

@T-Gro T-Gro commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Aligns Async.Await with fslang-suggestions #840, as requested in dotnet/fsharp#19785 (comment) and tracked by #676.

What

On cancellation, both Await overloads (Task<'T> and Task) now call the exception continuation with a TaskCanceledException (ec) instead of the cancellation continuation (cc):

-  elif task.IsCanceled then cc (TaskCanceledException ())
+  elif task.IsCanceled then ec (TaskCanceledException ())

Why

With cc, a cancelled Task propagated as an async cancellation, so a try ... with :? TaskCanceledException -> placed around the Await would not catch it. With ec it is surfaced as an ordinary exception, catchable locally — matching C# await and every AwaitTaskCorrect-derived implementation. The doc comment already described this behaviour.

Note: Async.map2/map3/zip build on Await, so a purely-cancelled input now surfaces as a TaskCanceledException rather than a cancellation. Where cancellation is combined with a fault, the fault still wins (cancellation dropped in Task.map* before reaching Await).

Tests

  • Asyncs.fs zip tests build their cancellation input directly (they previously relied on Await of a cancelled Task yielding a cancellation).
  • Added Task.fs coverage asserting a cancelled Task/Task<'T> await raises a TaskCanceledException catchable by try/with.

Based on tgro-fsharp10, so the diff against master also includes that branch's net10/net11 build-tracking work.

A cancelled Task is now surfaced through the exception continuation as a
TaskCanceledException (ec) instead of the cancellation continuation (cc), so it
can be caught by an ordinary try/with around the Await, matching C# await and
Async.AwaitTask.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@T-Gro
T-Gro force-pushed the tgro-align-await branch from e4c56af to 747e3f1 Compare August 10, 2026 08:59
@gusty
gusty requested a lite review from Copilot August 10, 2026 09:29
@gusty gusty linked an issue Aug 10, 2026 that may be closed by this pull request
@gusty
gusty self-requested a review August 10, 2026 09:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns Async.Await’s cancellation semantics with fslang-suggestions #840 by surfacing a canceled Task as a catchable TaskCanceledException via the exception continuation (rather than treating it as async cancellation), and updates tests accordingly.

Changes:

  • Updated Async.Await (for Task<'T> and Task) to route IsCanceled through the exception continuation (ec) with TaskCanceledException.
  • Updated async zip tests to use a truly canceled Async (via cancellation continuation) rather than relying on awaiting a canceled Task.
  • Added tests asserting a canceled Task/Task<'T> awaited via Async.Await raises a TaskCanceledException catchable by try/with.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/FSharpPlus/Extensions/Extensions.fs Changes Async.Await cancellation path to raise TaskCanceledException via exception continuation.
tests/FSharpPlus.Tests/Asyncs.fs Adjusts zip test inputs to produce genuine async cancellation independent of Async.Await behavior.
tests/FSharpPlus.Tests/Task.fs Adds coverage to ensure awaiting canceled tasks raises TaskCanceledException catchable by try/with.
Suppressed comments (1)

src/FSharpPlus/Extensions/Extensions.fs:200

  • Same as the generic overload: prefer TaskCanceledException(task) over TaskCanceledException() so the thrown exception is associated with the awaited task (matching typical await semantics) and preserves the Task reference for diagnostics.
                    if task.IsFaulted then
                        let e = Unchecked.nonNull task.Exception
                        if e.InnerExceptions.Count = 1 then ec e.InnerExceptions[0]
                        else ec e
                    elif task.IsCanceled then ec (TaskCanceledException ())
                    else sc ())

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 168 to 173
if task.IsFaulted then
let e = Unchecked.nonNull task.Exception
if e.InnerExceptions.Count = 1 then ec e.InnerExceptions[0]
else ec e
elif task.IsCanceled then cc (TaskCanceledException ())
elif task.IsCanceled then ec (TaskCanceledException ())
else sc task.Result)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Align Await implementation

4 participants