Add net9 TaskCompletionSource SetFromTask and TrySetFromTask - #593
Merged
Conversation
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.
This was referenced Sep 10, 2026
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
TaskCompletionSourceis a BCL type from net5.0 and a type Polyfill recreates below that. Anextension-stylethis TaskCompletionSourcebinds 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
Taskdoes not expose the token that canceled it, and the BCL reads it from internal state. The only public route is theOperationCanceledExceptionthe 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 onUnobservedTaskException— but it costs a throw and catch on the cancellation path, which is//Note:d on all four members. Verified that a token set viaTrySetCanceled(token)and viaTask.FromCanceled(token)both round-trip, on net11, net8.0 and net462.Semantics verified against net11 rather than assumed
ArgumentException("The provided task must have already completed.", "completedTask")— byTrySetFromTaskas well, which throws rather than returningfalse.AggregateExceptionproduces a destination with that same nestedAggregateExceptionas its one inner.CanBeCanceledisfalse.nullthrowsArgumentNullException(nameof(completedTask))from both.SetFromTaskthrowsInvalidOperationExceptionandTrySetFromTaskreturnsfalse.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
TimeSpanconstants 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 areCryptographicOperations.FixedTimeEqualsandCollectionsMarshal.SetCount, both the documented recreated-type-plus-extension pair collapsing to one signature).Duplicate section headers.
TaskCompletionSourceis listed byWriteTypeas a recreated type and now has extension members, which produced two#### TaskCompletionSourceheaders.WriteTypenow merges into the sectionWriteExtensionsalready opened, still counting the type;WriteTypeMethodsthrows 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.