C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
54: Cancellation Tokens
Ever started a long-running task in an app—maybe a big file export or a database sync—and then realized you made a mistake and hit "Cancel," only to find that the app just ignored you? It's frustrating for the user and a waste of server resources. In C#, we handle this using CancellationToken and CancellationTokenSource.
To show you how this actually works, let's build a simple Log Analyzer. Imagine we're scanning a massive text file for a specific error keyword. Since the file could be gigabytes in size, we need a way to kill the process if the user decides they don't want to wait anymore.
Simulating a Heavy Scan
First, I'll create a method that simulates reading through a file. I'm using a simple loop here to represent the processing of lines. Notice how I'm passing a CancellationToken as a parameter. This is the standard pattern in .NET; almost every async method in the framework that takes a long time will have a token parameter.
public async Task SearchLogsAsync(string keyword, CancellationToken token)
{
Console.WriteLine("Starting log scan...");
for (int i = 1; i <= 100; i++)
{
// Simulate heavy work
await Task.Delay(100, token);
Console.WriteLine($"Processing line {i}...");
}
Console.WriteLine("Scan complete!");
}
Wiring up the Cancellation Signal
The token itself is just a listener. To actually send the "stop" signal, we need a CancellationTokenSource (CTS). The CTS is the controller; the token is the messenger. I'll set up a simple console trigger to cancel the operation after a few seconds.
var cts = new CancellationTokenSource();
// Start the scan in the background
Task scanTask = SearchLogsAsync("CriticalError", cts.Token);
// Simulate the user clicking a "Cancel" button after 1 second
await Task.Delay(1000);
Console.WriteLine("User clicked cancel!");
cts.Cancel();
try
{
await scanTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("The scan was aborted successfully.");
}
The "Wait, it's still running!" Moment
Now, here is where I usually trip up when I'm rushing through a project. If I had a complex loop doing heavy CPU work—instead of just Task.Delay—the code above wouldn't actually stop immediately. I'll be honest: I've spent way too many hours debugging "zombie tasks" because I passed the token into the method but forgot to actually check it inside my logic.
If my loop was doing something like File.ReadAllLines() or heavy math, Task.Delay wouldn't be there to catch the cancellation. To fix this, I need to manually poll the token.
public async Task SearchLogsAsync(string keyword, CancellationToken token)
{
for (int i = 1; i <= 100; i++)
{
// This is the missing piece!
// It throws OperationCanceledException if cts.Cancel() was called.
token.ThrowIfCancellationRequested();
// Imagine some heavy CPU-bound regex scanning happening here
Thread.SpinWait(1000000);
Console.WriteLine($"Processing line {i}...");
}
}
Cleaning up the Exception
You'll notice I wrapped the await scanTask in a try-catch block for OperationCanceledException. This is crucial. When you call ThrowIfCancellationRequested(), it doesn't just "return" from the method; it throws an exception to unwind the stack. If you don't catch it, your application will crash. In a real UI app, you'd catch this to hide the loading spinner and show a "Scan Cancelled" message to the user.
One last tip: always remember to dispose of your CancellationTokenSource. It uses a timer internally, and if you're creating thousands of these in a high-throughput system, you'll see a memory leak. Wrap it in a using block or call cts.Dispose() once you're done with the operation.
📋 Practical Task
Building a Cancellable Image Downloader
Your task is to create a program that simulates downloading three large images from a server. Each "download" should be a method that takes a CancellationToken and uses a loop to simulate progress (e.g., printing "Downloading image 1: 10%... 20%...").
- Implement a
CancellationTokenSourcethat triggers a cancellation after 2 seconds. - Ensure that the download methods check the token frequently using
ThrowIfCancellationRequested(). - Handle the resulting
OperationCanceledExceptiongracefully so the program prints "Download sequence aborted by user" instead of crashing. - Ensure the
CancellationTokenSourceis properly disposed.
There are no comments for now.