Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 26, 2025

  • Create CA2027 analyzer to detect non-cancelable Task.Delay in Task.WhenAny
  • Remove code fixer (analyzer only as requested)
  • Update resource strings to be platform-agnostic
  • Fix method check ordering (type equality, IsStatic, then name)
  • Add logic to skip single-task WhenAny (avoid false positives)
  • Handle explicit arrays and collection expressions
  • Optimize to avoid allocations in common case (lazy list initialization)
  • Restore RulesMissingDocumentation.md entries (CA1873, CA1874, CA1875, CA2023)
  • Update tests to remove fixer references
  • Add tests for new scenarios (single task, explicit arrays)
  • Run msbuild /t:pack to update auto-generated documentation
  • All tests passing (15/15)

Latest Changes

RulesMissingDocumentation.md restoration:

  • The file previously had 4 entries (CA1873, CA1874, CA1875, CA2023) that were incorrectly removed
  • These represent existing rules with missing documentation, unrelated to CA2027
  • Restored all 4 entries to their original state

The analyzer is complete and all tests pass.

Original prompt

This section details on the original issue you should resolve

<issue_title>Detect non-cancelable Task.Delay passed to Task.WhenAny</issue_title>
<issue_description>Flag places where a Task.Delay is used as an argument to WhenAny and where that Task.Delay doesn't take a cancellation token, in which case the Task.Delay is likely leaving a timer running for longer than is necessary.

Category: Performance</issue_description>

<agent_instructions>Please create an analyzer (no fixer) for this in the netanalyzers project. It should flag cases where a call to Task.Delay is used as a direct argument to Task.WhenAny and when the only argument to the Task.Delay is a timeout value but no cancellation token.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@jeffhandley Estimates: * Analyzer: Medium * Fixer: Not Applicable @stephentoub > Fixer: Not Applicable

I think a fixer could reasonably be done here.

Also, it's such a common pattern, we could consider introducing new overloads to simplify it.</comment_new>
<comment_new>@stephentoub

Would a fixer simply check if the current method has a cancellationtoken parameter? Otherwise where would our cancellationtoken come from?

The fixer would manufacture it. Essentially this:

if (task != await Task.WhenAny(Task.Delay(timeout), task))
    throw new TimeoutException();

would become

using (var cts = new CancellationTokenSource())
{
    Task t = await Task.WhenAny(Task.Delay(timeout, cts.Token), task);
    cts.Cancel();
    if (t != task)
        throw new TimeoutException();
}

or something along those lines.

That said, a better approach might instead be to just add an API like dotnet/runtime#27723, as I expect that represents the 99% case for this pattern, and then a fixer here could offer to just switch to use that API if it's available.</comment_new>
<comment_new>@stephentoub
This is different from CA2016. In fact if CA2016 fired for the shown pattern, it would be doing the wrong thing (or at least it wouldn't be doing as well as it should). The idiom being used is simply trying to time out an await, and using a Task.Delay combined with a Task.WhenAny to do it, but then not disposing of the resources created for that timeout, namely the timer underneath the Task.Delay. So a dedicated CancellationTokenSource needs to be created that can be used to cancel the Task.Delay after the operation completes.

I think the right answer here is shipping some form of dotnet/runtime#27723, and then the analyzer would flag patterns like await Task.WhenAny(task, Task.Delay(timeout)) and recommend switching to that new API.
cc: @eiriktsarpalis </comment_new>
<comment_new>@stephentoub
There are lots of variations to the erroneous pattern, so it might be hard to flag them all as well as come up with a good fixer. But we could probably just flag cases where Task.Delay(timeout) (no cancellation token argument) is passed to Task.WhenAny with just one other task, e.g.

Task.WhenAny(task, Task.Delay(timeout))

and suggest that it's likely an issue and Task.WaitAsync should be used instead.</comment_new>
<comment_new>@bartonjs
Video

Seems good as proposed. Steve and Stephen pointed out that it's eventually a problem beyond performance, so the category is now Reliability.

  • Analyzer to detect when Task.Delay with no cancellation token is used in Task.WhenAny, using the next available diagnostic ID.
  • A fixer which suggests to use WaitAsync with a timeout over the WhenAny approach, in the cases where it is safe to do so (and the TFM has WaitAsync available).
  • The docs page for the diagnostic should demonstrate with "with cancellation token" pattern for older TFMs, and perhaps talk about when some other token is available.

Severity: Info
Category: Reliability</comment_new>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Detect non-cancelable Task.Delay in Task.WhenAny Add CA2027: Detect non-cancelable Task.Delay in Task.WhenAny Oct 26, 2025
Copilot AI requested a review from stephentoub October 26, 2025 03:00
Copilot finished work on behalf of stephentoub October 26, 2025 03:00
Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

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

@copilot, see the build failures:

  One or more auto-generated documentation files were either edited manually, or not updated. Please revert changes made to the following files (if manually edited) and run `msbuild /t:pack` at the root of the repo to automatically update them:
      D:\a\_work\1\s\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers.md
      D:\a\_work\1\s\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers.sarif

Copy link
Contributor Author

Copilot AI commented Oct 26, 2025

@copilot, see the build failures:

  One or more auto-generated documentation files were either edited manually, or not updated. Please revert changes made to the following files (if manually edi...

Fixed in c5ddc49. Ran msbuild /t:pack to update the auto-generated documentation files (Microsoft.CodeAnalysis.NetAnalyzers.md and .sarif).

Copilot AI requested a review from stephentoub October 26, 2025 03:25
Copilot finished work on behalf of stephentoub October 26, 2025 03:25
Copilot AI requested a review from stephentoub October 26, 2025 14:54
Copilot finished work on behalf of stephentoub October 26, 2025 14:54
@stephentoub stephentoub marked this pull request as ready for review October 26, 2025 14:57
@stephentoub stephentoub requested a review from a team as a code owner October 26, 2025 14:57
Copilot AI review requested due to automatic review settings October 26, 2025 14:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements analyzer CA2027 to detect non-cancelable Task.Delay calls used with Task.WhenAny, which can waste resources by leaving timers running after the operation completes. The analyzer provides platform-agnostic guidance recommending Task.WaitAsync when available or the CancellationToken pattern otherwise.

Key changes:

  • Created analyzer-only rule (no code fixer) that flags Task.Delay calls without cancellation tokens in Task.WhenAny operations
  • Skips single-task scenarios to avoid false positives
  • Supports both implicit params arrays and explicit array/collection expressions

Reviewed Changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated no comments.

Show a summary per file
File Description
DoNotUseNonCancelableTaskDelayWithWhenAny.cs Core analyzer implementation detecting non-cancelable Task.Delay in Task.WhenAny
DoNotUseNonCancelableTaskDelayWithWhenAnyTests.cs Comprehensive test coverage for C# and VB scenarios
MicrosoftNetCoreAnalyzersResources.resx English resource strings for diagnostic messages
MicrosoftNetCoreAnalyzersResources.*.xlf Localization files marking new strings for translation
DiagnosticCategoryAndIdRanges.txt Updates reliability range to include CA2027
AnalyzerReleases.Unshipped.md Documents CA2027 as new reliability rule
Microsoft.CodeAnalysis.NetAnalyzers.sarif SARIF metadata for CA2027
Microsoft.CodeAnalysis.NetAnalyzers.md Auto-generated documentation for CA2027
RulesMissingDocumentation.md Reverted unrelated changes

Copilot finished work on behalf of stephentoub October 26, 2025 16:02
Copilot AI requested a review from stephentoub October 26, 2025 16:02
Copilot finished work on behalf of stephentoub October 26, 2025 16:26
@stephentoub stephentoub force-pushed the copilot/add-task-delay-analyzer branch from 983c118 to 6415242 Compare October 26, 2025 18:23
@stephentoub stephentoub enabled auto-merge (squash) October 26, 2025 18:23
auto-merge was automatically disabled October 27, 2025 19:03

Head branch was pushed to by a user without write access

Copilot finished work on behalf of tarekgh October 27, 2025 19:04
Copilot AI requested a review from tarekgh October 27, 2025 19:04
@tarekgh
Copy link
Member

tarekgh commented Oct 27, 2025

@stephentoub just to make sure you saw the comment #51452 (comment).

Copilot AI requested a review from stephentoub October 27, 2025 19:52
Copilot finished work on behalf of stephentoub October 27, 2025 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect non-cancelable Task.Delay passed to Task.WhenAny

3 participants