Async .NET · Lesson 10

Race a completion

Observe which operation completes without losing the remaining work.

How it works

WhenAny returns the task that completed first; it does not cancel the other input tasks. The already completed task wins this example predictably, and awaiting the winner obtains its result. The pending timer still belongs to the caller and must be cancelled or observed according to its lifetime. Here the timer has a source that is cancelled and its cancellation is awaited.

In real timeout code, deciding that the caller will stop waiting is separate from deciding that the underlying operation should stop. Make both decisions explicit so abandoned work does not continue consuming resources after the interface reports completion.

List the tasks that remain owned after WhenAny returns. Explain why a user-visible timeout message alone would not prove that an underlying import or network operation had actually stopped.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

using var source = new CancellationTokenSource();
var ready = Task.FromResult(7);
var pending = Task.Delay(TimeSpan.FromMinutes(1), source.Token);
var winner = await Task.WhenAny(ready, pending);
Console.WriteLine(ReferenceEquals(winner, ready));
source.Cancel();
try { await pending; } catch (OperationCanceledException) { }
Console.WriteLine(await ready);

Expected output

True
7
Try this
WhenAny selects a completion; it does not stop the other operations.