Curriculum
Cancellation Tokens, Thread Safety, Concurrent Collections, and Producer-Consumer Pattern in C# are advanced concurrency concepts that help developers build scalable, responsive, and reliable applications. In modern enterprise systems, tasks may run for several minutes or even hours, users may cancel operations, and multiple threads may access shared resources simultaneously. Cancellation Tokens, Thread Safety, Concurrent Collections, and Producer-Consumer Pattern in C# provide solutions for these real-world challenges.
These concepts are heavily used in ASP.NET Core Applications, Cloud Applications, Background Services, Microservices, Banking Systems, E-Commerce Platforms, Data Processing Systems, and Enterprise Software Solutions.
Consider a scenario:
User Starts Report Generation
Report Takes 10 Minutes
User Closes Application
Without cancellation:
Report Continues Running
Resources are wasted.
With Cancellation Tokens:
Operation Stops Gracefully
This improves performance and user experience.
A Cancellation Token is a mechanism used to cancel a running task safely.
Namespace:
using System.Threading;
Main Components:
CancellationTokenSource
CancellationToken
These work together to manage cancellation requests.
CancellationTokenSource creates and controls cancellation requests.
Example:
CancellationTokenSource
source =
new CancellationTokenSource();
This object can signal cancellation to one or more tasks.
Example:
CancellationToken token =
source.Token;
The token is passed to running operations.
Example:
CancellationTokenSource
source =
new CancellationTokenSource();
CancellationToken token =
source.Token;
Task:
Task.Run(() =>
{
for(int i = 1;
i <= 100;
i++)
{
if(token.IsCancellationRequested)
{
Console.WriteLine(
"Cancelled");
return;
}
Console.WriteLine(i);
Thread.Sleep(100);
}
});
Cancel:
source.Cancel();
Output:
Cancelled
The operation stops gracefully.
Example:
public async Task
ProcessAsync(
CancellationToken token)
{
await Task.Delay(
5000,
token);
}
Usage:
await ProcessAsync(
token);
This is the recommended approach in ASP.NET Core applications.
Thread Safety means code behaves correctly when accessed by multiple threads simultaneously.
Unsafe Example:
int counter = 0;
counter++;
If multiple threads execute:
Incorrect Results
may occur.
This is called a race condition.
Example:
Initial Value:
Counter = 100
Thread A:
Reads 100
Thread B:
Reads 100
Both increment:
101
Expected:
102
Actual:
101
Data corruption occurs.
Using lock:
private static object
locker =
new object();
Example:
lock(locker)
{
counter++;
}
Only one thread can modify the data at a time.
Interlocked provides atomic operations.
Example:
Interlocked.Increment(
ref counter);
Benefits:
Useful for counters and statistics.
Standard collections:
List<T>
Dictionary<TKey,TValue>
Queue<T>
are not thread-safe.
Microsoft provides Concurrent Collections for multithreaded applications.
Namespace:
using System.Collections.Concurrent;
These collections support concurrent access.
Thread-safe version of Dictionary.
Example:
ConcurrentDictionary<
int,
string>
students =
new ConcurrentDictionary<
int,
string>();
Adding Data:
students.TryAdd(
1,
"Rahul");
Retrieving Data:
students[1];
ConcurrentDictionary is widely used in enterprise systems.
Thread-safe Queue.
Example:
ConcurrentQueue<string>
queue =
new ConcurrentQueue<string>();
Add:
queue.Enqueue(
"Task 1");
Remove:
queue.TryDequeue(
out string task);
Useful for background processing.
Thread-safe Stack.
Example:
ConcurrentStack<string>
stack =
new ConcurrentStack<string>();
Push:
stack.Push(
"Item");
Pop:
stack.TryPop(
out string item);
Safe for concurrent access.
BlockingCollection is one of the most important concurrent collections.
Features:
Example:
BlockingCollection<int>
numbers =
new BlockingCollection<int>();
This collection is heavily used in enterprise applications.
Producer-Consumer Pattern is a design pattern where:
Producer Creates Data
Consumer Processes Data
The producer and consumer work independently.
This pattern is extremely common in enterprise systems.
Producer:
Order System
Consumer:
Invoice Generator
Workflow:
Create Order
Add to Queue
Generate Invoice
The systems remain loosely coupled.
Example:
BlockingCollection<int>
queue =
new BlockingCollection<int>();
Task.Run(() =>
{
for(int i = 1;
i <= 10;
i++)
{
queue.Add(i);
Console.WriteLine(
"Produced " + i);
}
queue.CompleteAdding();
});
The producer adds items.
Example:
Task.Run(() =>
{
foreach(int item
in queue.GetConsumingEnumerable())
{
Console.WriteLine(
"Consumed " + item);
}
});
The consumer processes items automatically.
Benefits:
Many large-scale systems use this pattern.
Example:
CancellationTokenSource
source =
new CancellationTokenSource();
Consumer:
foreach(var item
in queue.GetConsumingEnumerable(
source.Token))
{
}
Cancellation becomes straightforward.
Example:
private static readonly
Lazy<Logger>
instance =
new Lazy<Logger>(
() => new Logger());
Benefits:
Frequently used in enterprise applications.
Transaction Processing
Audit Logging
Notification Services
Order Processing
Inventory Updates
Payment Verification
Patient Monitoring
Appointment Queues
Report Generation
Attendance Processing
Result Calculation
Notification Systems
These concepts are used daily in production environments.
Long-running tasks may waste resources.
Can cause race conditions.
May reduce performance.
Can create synchronization problems.
Can corrupt application data.
A Cancellation Token allows running operations to be cancelled safely.
Thread Safety ensures correct behavior when multiple threads access shared resources.
A Race Condition occurs when multiple threads modify shared data simultaneously.
A thread-safe version of Dictionary.
A thread-safe collection designed for Producer-Consumer scenarios.
A design pattern where producers generate data and consumers process it independently.
A Cancellation Token is used to cancel running tasks safely.
Thread Safety prevents race conditions and data corruption.
ConcurrentDictionary is a thread-safe key-value collection.
BlockingCollection is a thread-safe collection commonly used in Producer-Consumer systems.
It is a design pattern where one component produces data and another component processes it.
They help developers build scalable, reliable, high-performance, and enterprise-ready applications.
WhatsApp us