How it works
A caller may need to stop waiting even when the underlying operation does not support cancellation. WaitAsync can represent that caller-specific wait. In the sample, an already-canceled token causes the wait to throw, while the original operation's task remains incomplete. The distinction is visible in the printed false.
The operation can still complete later. Setting its result to seven and awaiting the original task succeeds. Cancellation of the wrapper did not change the original TaskCompletionSource or force it into a canceled state. This can be useful when several callers share one operation but have different patience or lifetime requirements.
Stopping only the wait creates an ownership obligation. Someone must still account for the continuing operation and observe a possible later fault. Abandoning the reference because the caller moved on can hide failures and leave resource usage detached from any clear owner.
A token is not a universal stop command. To cancel the operation itself, pass a token into an implementation that cooperatively observes it and defines its cleanup behavior. WaitAsync cannot retroactively add that cooperation to an arbitrary task. Decide whether the requirement concerns operation lifetime, waiting lifetime, or both.
Practice by completing the original operation with an exception after the canceled wait. Retain the task and observe that later fault in a separate try/catch. Then run two waits with different tokens against one operation and explain why canceling one wait should not automatically cancel the shared work for the other caller.
Finally, document who owns a shared task and who owns each wait. A screen closing may end one wait while a cache continues filling, whereas an application shutdown may require canceling the actual load. The same user action can imply different policies at different boundaries, so name cancellation sources after their actual responsibility.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
var operation = new TaskCompletionSource<int>();
using var caller = new CancellationTokenSource();
caller.Cancel();
try { await operation.Task.WaitAsync(caller.Token); }
catch (OperationCanceledException) { Console.WriteLine("wait canceled"); }
Console.WriteLine(operation.Task.IsCompleted);
operation.SetResult(7);
Console.WriteLine(await operation.Task);
Expected output
wait canceled False 7
WaitAsync controls the wait; only the operation owner can implement stopping the work.