Async .NET · Lesson 19

Canceled tasks are distinct from faulted tasks

Inspect terminal states without treating cooperative cancellation as an ordinary error.

How it works

A task can finish successfully, fault, or be canceled. All three are terminal states, so IsCompleted does not mean success. The sample creates a task already completed through cancellation and prints true for IsCanceled, false for IsFaulted, and true for IsCompleted. Those facts are compatible rather than contradictory.

Awaiting a canceled task propagates an OperationCanceledException or a derived cancellation exception. The caught exception carries the token used to create this canceled task, which the final comparison confirms. A caller can use that association when deciding whether the cancellation belongs to an expected lifetime.

Task.FromCanceled requires a token whose cancellation has already been requested. It is not a way to manufacture an arbitrary task that will cancel later. A running implementation should observe an appropriate token cooperatively and finish according to its contract, rather than merely labeling an unfinished operation as canceled.

Do not report every completed task as a successful result or every cancellation as a defect. A user pressing Stop may produce an expected canceled outcome, while a fault indicates a different failure path. The interface can present those outcomes differently without erasing diagnostic information needed for an actual error.

Practice by replacing the canceled task with Task.FromResult(5) and Task.FromException<int>(new InvalidOperationException()). Build a small table of IsCompleted, IsCanceled, IsFaulted, and the behavior of await for each task. Keep the result access inside the appropriate success or await handling path.

Finally, combine canceled and successful tasks with WhenAll, then add a faulted task in a separate experiment. Observe the combined terminal state rather than inferring it from only one input. Reliable async reporting describes both the group outcome and any per-operation details the caller needs to decide whether partial work can be used.

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 token = new CancellationToken(canceled: true);
Task<int> task = Task.FromCanceled<int>(token);
Console.WriteLine(task.IsCanceled);
Console.WriteLine(task.IsFaulted);
Console.WriteLine(task.IsCompleted);
try { await task; }
catch (OperationCanceledException error) { Console.WriteLine(error.CancellationToken == token); }

Expected output

True
False
True
True
Try this
Completed includes successful, faulted, and canceled terminal states.