C# System.Threading
Multithreading and asynchronous programming
โก What is System.Threading?
System.Threading enables concurrent programming by running multiple operations simultaneously. It helps create responsive applications that can perform background tasks, handle multiple users, and utilize modern multi-core processors efficiently.
// Simple thread example
using System.Threading;
Thread thread = new Thread(() => Console.WriteLine("Hello from thread!"));
thread.Start();
Core Threading Concepts
Thread
Create and manage threads
Thread t = new Thread(Method);
t.Start();
Task
Modern async operations
Task task = Task.Run(() => Work());
await task;
Lock
Synchronize access
lock (lockObject) {
// Thread-safe code
}
Sleep
Pause thread execution
Thread.Sleep(1000);
await Task.Delay(1000);
๐น Creating and Starting Threads
Threads in C# enable concurrent execution, allowing multiple operations to run simultaneously. By instantiating the Thread class and providing a method delegate, you can start parallel tasks, improving performance for CPU-intensive or blocking operations. Threads are managed by the OS and can run independently, making them suitable for background processing, real-time updates, and responsive UI. However, they require careful synchronization to avoid issues like race conditions or deadlocks.
using System;
using System.Threading;
class Program
{
static void Main()
{
Console.WriteLine("Main thread started");
// Create thread with method
Thread thread1 = new Thread(PrintNumbers);
thread1.Start();
// Create thread with lambda
Thread thread2 = new Thread(() =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Thread 2: {i}");
Thread.Sleep(300);
}
});
thread2.Start();
// Main thread continues
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Main: {i}");
Thread.Sleep(200);
}
// Wait for threads to complete
thread1.Join();
thread2.Join();
Console.WriteLine("All threads completed");
}
static void PrintNumbers()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Thread 1: {i}");
Thread.Sleep(400);
}
}
}
Output:
Main thread started
Main: 0
Thread 2: 0
Thread 1: 0
Main: 1
Thread 2: 1
Main: 2
Thread 1: 1
All threads completed
๐น Task-Based Async Programming
Task-based asynchronous programming in C# simplifies parallel execution using the Task Parallel Library (TPL). Tasks represent asynchronous operations and are more efficient than raw threads, as they utilize the thread pool optimally. Methods like Task.Run() and Task.WhenAll() help execute and coordinate multiple tasks. This model enhances scalability, especially in I/O-bound applications like web requests or file operations, by reducing resource overhead and improving responsiveness.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Starting tasks...");
// Create and start task
Task task1 = Task.Run(() => DoWork("Task 1", 1000));
// Create task with return value
Task<int> task2 = Task.Run(() =>
{
Console.WriteLine("Task 2 calculating...");
Task.Delay(500).Wait();
return 42;
});
// Async method
Task task3 = DoWorkAsync("Task 3", 800);
// Wait for all tasks
await Task.WhenAll(task1, task2, task3);
// Get result from task2
int result = await task2;
Console.WriteLine($"Task 2 result: {result}");
Console.WriteLine("All tasks completed");
}
static void DoWork(string name, int delay)
{
Console.WriteLine($"{name} started");
Task.Delay(delay).Wait();
Console.WriteLine($"{name} completed");
}
static async Task DoWorkAsync(string name, int delay)
{
Console.WriteLine($"{name} started");
await Task.Delay(delay);
Console.WriteLine($"{name} completed");
}
}
Output:
Starting tasks...
Task 1 started
Task 2 calculating...
Task 3 started
Task 3 completed
Task 1 completed
Task 2 result: 42
All tasks completed
๐น Async and Await Keywords
The async and await keywords in C# enable clean, readable asynchronous programming. By marking a method with async, you allow it to use await to pause execution without blocking the thread, perfect for I/O operations like API calls. For example, await client.GetStringAsync(url) frees the thread while waiting. This improves app responsiveness and scalability, especially in web and mobile applications where waiting for network data is common.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Starting async operations...");
// Call async method
string result1 = await FetchDataAsync("API 1");
Console.WriteLine($"Result: {result1}");
// Run multiple async operations in parallel
Task<string> task1 = FetchDataAsync("API 2");
Task<string> task2 = FetchDataAsync("API 3");
string[] results = await Task.WhenAll(task1, task2);
Console.WriteLine("All results:");
foreach (string result in results)
{
Console.WriteLine($"- {result}");
}
}
static async Task<string> FetchDataAsync(string source)
{
Console.WriteLine($"Fetching from {source}...");
// Simulate async operation
await Task.Delay(1000);
Console.WriteLine($"{source} completed");
return $"Data from {source}";
}
}
Output:
Starting async operations...
Fetching from API 1...
API 1 completed
Result: Data from API 1
Fetching from API 2...
Fetching from API 3...
API 2 completed
API 3 completed
All results:
- Data from API 2
- Data from API 3
๐น Thread Synchronization with Lock
The lock statement ensures thread-safe access to shared resources in multithreaded applications. It prevents race conditions by allowing only one thread at a time to execute a critical section of code. For instance, lock (lockObject) { counter++; } guarantees accurate increments. Without synchronization, concurrent writes can corrupt data, leading to unpredictable results. Always lock on a private object to avoid deadlocks and maintain application integrity.
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static int counter = 0;
static object lockObject = new object();
static void Main()
{
// Without lock - may produce incorrect results
counter = 0;
Task[] tasks1 = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks1[i] = Task.Run(() => IncrementWithoutLock());
}
Task.WaitAll(tasks1);
Console.WriteLine($"Without lock: {counter}");
// With lock - always correct
counter = 0;
Task[] tasks2 = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks2[i] = Task.Run(() => IncrementWithLock());
}
Task.WaitAll(tasks2);
Console.WriteLine($"With lock: {counter}");
}
static void IncrementWithoutLock()
{
for (int i = 0; i < 1000; i++)
{
counter++;
}
}
static void IncrementWithLock()
{
for (int i = 0; i < 1000; i++)
{
lock (lockObject)
{
counter++;
}
}
}
}
Output:
Without lock: 4823
With lock: 5000
๐น Thread Sleep and Delay
Thread.Sleep and Task.Delay both introduce pauses but behave differently. Thread.Sleep blocks the current thread, making it unsuitable for async contexts. In contrast, Task.Delay returns a task that completes after a specified time, freeing the thread for other work. Use Task.Delay in asynchronous methods for non-blocking waits, such as polling or implementing timeouts, ensuring efficient resource utilization.
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Demonstrating delays...");
// Thread.Sleep (blocks thread)
Console.WriteLine("Starting Thread.Sleep...");
Thread.Sleep(1000);
Console.WriteLine("Thread.Sleep completed (1 second)");
// Task.Delay (async, doesn't block)
Console.WriteLine("\nStarting Task.Delay...");
await Task.Delay(1000);
Console.WriteLine("Task.Delay completed (1 second)");
// Countdown example
Console.WriteLine("\nCountdown:");
for (int i = 5; i > 0; i--)
{
Console.WriteLine(i);
await Task.Delay(500);
}
Console.WriteLine("Go!");
// Periodic task
Console.WriteLine("\nPeriodic updates:");
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"Update {i + 1}");
await Task.Delay(800);
}
}
}
Output:
Demonstrating delays...
Starting Thread.Sleep...
Thread.Sleep completed (1 second)
Starting Task.Delay...
Task.Delay completed (1 second)
Countdown:
5
4
3
2
1
Go!
๐น CancellationToken
A CancellationToken enables cooperative cancellation of long-running or asynchronous operations. By passing the token to methods and periodically checking cancellationToken.IsCancellationRequested, you can gracefully stop tasks when needed. This prevents resource leaks and improves user experience by allowing responsive interruptions, such as canceling a file download or stopping a background data fetch without forcing the application to hang or crash.
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Create cancellation token source
CancellationTokenSource cts = new CancellationTokenSource();
// Start long-running task
Task task = LongRunningOperationAsync(cts.Token);
// Cancel after 2 seconds
Console.WriteLine("Task started. Will cancel in 2 seconds...");
await Task.Delay(2000);
cts.Cancel();
Console.WriteLine("Cancellation requested");
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled");
}
Console.WriteLine("Program completed");
}
static async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
// Check if cancellation requested
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i + 1}/10");
await Task.Delay(500, token);
}
Console.WriteLine("Operation completed");
}
}
Output:
Task started. Will cancel in 2 seconds...
Working... 1/10
Working... 2/10
Working... 3/10
Working... 4/10
Cancellation requested
Task was cancelled
Program completed