Skip to content

Add net9 TaskCompletionSource SetFromTask and TrySetFromTask - #593

Merged
SimonCropp merged 1 commit into
mainfrom
taskcompletionsource-setfromtask
Sep 10, 2026
Merged

Add net9 TaskCompletionSource SetFromTask and TrySetFromTask#593
SimonCropp merged 1 commit into
mainfrom
taskcompletionsource-setfromtask

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

Four members, on both the non-generic and the generic TaskCompletionSource. These are net9, not net10 as the earlier diff summary had them — confirmed absent from the 8.0.31 ref pack and present in 9.0.20 — so the guard is !NET9_0_OR_GREATER.

One implementation for every target

The non-generic TaskCompletionSource is a BCL type from net5.0 and a type Polyfill recreates below that. An extension-style this TaskCompletionSource binds to whichever is in scope, so both windows are covered by the same code rather than by instance methods on the recreated type plus a separate extension for net5.0–net8.0.

Recovering the cancellation token

Task does not expose the token that canceled it, and the BCL reads it from internal state. The only public route is the OperationCanceledException the task throws when observed, so a canceled source task is observed to recover it. That is safe — the task is already complete, and observing a canceled task has no bearing on UnobservedTaskException — but it costs a throw and catch on the cancellation path, which is //Note:d on all four members. Verified that a token set via TrySetCanceled(token) and via Task.FromCanceled(token) both round-trip, on net11, net8.0 and net462.

Semantics verified against net11 rather than assumed

  • An incomplete task is rejected with ArgumentException("The provided task must have already completed.", "completedTask") — by TrySetFromTask as well, which throws rather than returning false.
  • A faulted task's inner exceptions are carried over by instance and are not flattened: a source whose single inner is itself an AggregateException produces a destination with that same nested AggregateException as its one inner.
  • A task canceled with no token yields a token where CanBeCanceled is false.
  • null throws ArgumentNullException(nameof(completedTask)) from both.
  • On an already-completed source, SetFromTask throws InvalidOperationException and TrySetFromTask returns false.

All six new tests run on every target, so on net9.0+ they exercise the BCL and below it the polyfill.

Two api_list defects from #592

Adding this surfaced both.

Notes were emitted twice. Where a declaration has no attribute list its first token is its first modifier, so the two trivia sequences #592 concatenated are the same trivia. Every note on an unattributed member was duplicated — all 15 TimeSpan constants among them. Deduplicated by position: the note count drops from 107 to 61, which now matches the source exactly (cross-checked note-for-note; the only two members with more occurrences in source than in the list are CryptographicOperations.FixedTimeEquals and CollectionsMarshal.SetCount, both the documented recreated-type-plus-extension pair collapsing to one signature).

Duplicate section headers. TaskCompletionSource is listed by WriteType as a recreated type and now has extension members, which produced two #### TaskCompletionSource headers. WriteType now merges into the section WriteExtensions already opened, still counting the type; WriteTypeMethods throws rather than writing members under a duplicate header, so the next occurrence fails loudly instead of silently.

Result

API count 1091 → 1095.

Solution clean in Release, Consume clean across all 22 TFMs, tests green on net11.0 (1680), net10.0 (1680), net8.0 (1677), net462 (1646), plus PublicTests, EmbeddedTests, UnsafeTests, NoRefsTests and NoExtrasTests.

Covers both the non-generic and the generic TaskCompletionSource. On frameworks
without the non-generic type the extension binds to the one Polyfill recreates,
so a single implementation serves every target.

Task does not expose the token that canceled it, so for a canceled source task
the token is recovered from the OperationCanceledException the task throws when
observed. Noted, since it costs a throw and catch the BCL avoids.

Verified against net11: an incomplete task is rejected by TrySetFromTask with an
ArgumentException rather than returning false, faulted inner exceptions are
carried over by instance and are not flattened, and a canceled task with no token
yields a token that cannot be canceled.

Also fixes two api_list defects from #592:

* Notes were emitted twice for members without an attribute list, since the
  declaration's leading trivia and its first modifier's leading trivia are the
  same trivia. Deduplicated by position, taking the note count from 107 to 61,
  which now matches the source exactly.
* A recreated type listed with WriteType emitted a second header of the same
  name when the type also had extension members, as TaskCompletionSource now
  does. WriteType merges into the existing section; WriteHelper throws rather
  than writing members under a duplicate header.

API count 1091 -> 1095.
@SimonCropp SimonCropp added this to the 11.3.0 milestone Sep 10, 2026
@SimonCropp
SimonCropp merged commit bee106b into main Sep 10, 2026
4 of 6 checks passed
@SimonCropp
SimonCropp deleted the taskcompletionsource-setfromtask branch September 10, 2026 04:01
This was referenced Sep 10, 2026
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.

1 participant