Skip to content

Replace Task.WhenAny + Task.Delay timeout pattern with .WaitAsync(TimeSpan)#805

Merged
SteveSandersonMS merged 2 commits intomainfrom
copilot/replace-task-whenany-with-waitasync
Mar 12, 2026
Merged

Replace Task.WhenAny + Task.Delay timeout pattern with .WaitAsync(TimeSpan)#805
SteveSandersonMS merged 2 commits intomainfrom
copilot/replace-task-whenany-with-waitasync

Conversation

Copy link
Contributor

Copilot AI commented Mar 12, 2026

Task.WhenAny(task, Task.Delay(timeout)) leaks an undisposed timer and requires a separate Assert.Equal to detect timeout. .WaitAsync(TimeSpan) is simpler and throws TimeoutException natively on timeout, failing the test without extra assertions.

Changes

  • dotnet/test/SessionTests.cs — replace 2-line WhenAny/Assert.Equal with a single WaitAsync call
  • dotnet/test/MultiClientTests.cs — eliminate the timeout variable and Assert.Equal; chain .WaitAsync directly onto Task.WhenAll
// Before
var timeout = Task.Delay(TimeSpan.FromSeconds(10));
var allEvents = Task.WhenAll(client1Requested.Task, client2Requested.Task, ...);
Assert.Equal(allEvents, await Task.WhenAny(allEvents, timeout));

// After
await Task.WhenAll(client1Requested.Task, client2Requested.Task, ...)
    .WaitAsync(TimeSpan.FromSeconds(10));
Original prompt

The Task.WhenAny(task, Task.Delay(timeout)) pattern is used in tests to implement timeouts, but it has downsides: it creates a fire-and-forget Task.Delay timer that isn't disposed, and the pattern is more verbose than necessary. Replace all occurrences with the simpler .WaitAsync(TimeSpan) API.

There are two occurrences to fix:

1. dotnet/test/SessionTests.cs (around line 275)

Current code:

var completed = await Task.WhenAny(idleReceived.Task, Task.Delay(TimeSpan.FromSeconds(60)));
Assert.Equal(idleReceived.Task, completed);

Replace with:

await idleReceived.Task.WaitAsync(TimeSpan.FromSeconds(60));

The Assert.Equal check is no longer needed because .WaitAsync will throw a TimeoutException if the task doesn't complete in time, which will fail the test.

2. dotnet/test/MultiClientTests.cs (around line 141)

Current code:

var timeout = Task.Delay(TimeSpan.FromSeconds(10));
var allEvents = Task.WhenAll(
    client1Requested.Task, client2Requested.Task,
    client1Completed.Task, client2Completed.Task);
Assert.Equal(allEvents, await Task.WhenAny(allEvents, timeout));

Replace with:

await Task.WhenAll(
    client1Requested.Task, client2Requested.Task,
    client1Completed.Task, client2Completed.Task).WaitAsync(TimeSpan.FromSeconds(10));

The timeout variable and the Assert.Equal are no longer needed.

Please also search the entire repository for any other occurrences of this same pattern (Task.WhenAny combined with Task.Delay for timeout purposes) and fix them similarly.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Create a PR to fix this to use .WaitAsync(TimeSpan.FromSeconds(60)) instead. Also search for any other occurrences of the same pattern (using a Task.Delay with WhenAny) and fix them.

Assistant:

I'll start by searching for all occurrences of the Task.WhenAny + Task.Delay pattern in the repository, and then create a PR to fix them all.

Assistant: I found two occurrences of the Task.WhenAny + Task.Delay pattern:

  1. dotnet/test/SessionTests.cs (line ~275):

    var completed = await Task.WhenAny(idleReceived.Task, Task.Delay(TimeSpan.FromSeconds(60)));
    Assert.Equal(idleReceived.Task, completed);
  2. dotnet/test/MultiClientTests.cs (line ~141):

    var timeout = Task.Delay(TimeSpan.FromSeconds(10));
    var allEvents = Task.WhenAll(
        client1Requested.Task, client2Requested.Task,
        client1Completed.Task, client2Completed.Task);
    Assert.Equal(allEvents, await Task.WhenAny(allEvents, timeout));

Let me create the PR to fix both.

Assistant: I've kicked off the Copilot coding agent to create a PR in github/copilot-sdk that replaces all instances of the Task.WhenAny(..., Task.Delay(...)) timeout pattern with the cleaner .WaitAsync(TimeSpan) API.

From my search, I found at least two occurrences:

  1. dotnet/test/SessionTests.cs (line 275)await Task.WhenAny(idleReceived.Task, Task.Delay(TimeSpan.FromSeconds(60))) with an Assert.Equal check
  2. dotnet/test/MultiClientTests.cs (around line 141)Task.WhenAny(allEvents, timeout) where timeout = Task.Delay(TimeSpan.FromSeconds(10))

Both will be replaced with .WaitAsync(TimeSpan), which throws a TimeoutException on timeout (failing the test naturally), eliminating the need for the separate Task.Delay variable and Assert.Equal checks.

Note: The search results are limited to 10 results, so there may be additional occurrences. The coding agent will do a thorough search of the full repository. You can view more results in the GitHub code search UI.

You'll receive the PR link shortly from the coding agent once it's ready.

This pull request was created from Copilot chat.


📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor tests to use WaitAsync for timeouts Replace Task.WhenAny + Task.Delay timeout pattern with .WaitAsync(TimeSpan) Mar 12, 2026
@stephentoub stephentoub marked this pull request as ready for review March 12, 2026 17:08
@stephentoub stephentoub requested a review from a team as a code owner March 12, 2026 17:08
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

Updates .NET test timeouts to use Task.WaitAsync(TimeSpan) (net8) instead of manual Task.WhenAny + assertions, simplifying async wait logic in the SDK’s test suite.

Changes:

  • Replace Task.WhenAny(..., Task.Delay(...)) + equality assertions with WaitAsync(TimeSpan) in session event tests.
  • Apply the same WaitAsync(TimeSpan) pattern to multi-client broadcast event synchronization.

Reviewed changes

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

File Description
dotnet/test/SessionTests.cs Simplifies waiting for session.idle by using WaitAsync(60s) instead of WhenAny + assert.
dotnet/test/MultiClientTests.cs Simplifies waiting for broadcast tool events across two clients using WhenAll(...).WaitAsync(10s).

You can also share your feedback on Copilot code review. Take the survey.

@SteveSandersonMS SteveSandersonMS added this pull request to the merge queue Mar 12, 2026
Merged via the queue into main with commit b67e3e5 Mar 12, 2026
22 checks passed
@SteveSandersonMS SteveSandersonMS deleted the copilot/replace-task-whenany-with-waitasync branch March 12, 2026 22:39
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.

4 participants