I have a framework which creates a CancellationTokenSource, configures CancelAfter, then calls an async method and passes the Token. The async method then spawns many tasks, pas
Provide the CancellationToken to Task.Run in addition to passing it to the method doing the work. When you do this Task.Run can see that the exception thrown was caused by the CancellationToken it was given, and will mark the Task as cancelled.
tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken),
cancelationToken));
Once you've done this you can ensure that DoWork throws when the token is cancelled, rather than checking IsCancellationRequested to try to end by being marked as "completed successfully".