Multithreading in C# is a technique that allows a program to perform multiple operations at the same time by executing different threads concurrently. It is commonly used to improve performance, responsiveness, and efficient resource utilization in applications.
C# provides multithreading support through the System. Threading namespace and higher-level abstractions like Task Parallel Library (TPL) and async/await.
What is a Thread?
A thread is the smallest unit of execution in a program. Every C# program starts with a main thread created automatically by the runtime. You can create additional threads to run tasks in parallel with the main thread.
Creating Threads in C#
C# provides multiple ways to create threads:
1. Using Thread Class
The Thread class in the System. Threading namespace allows explicit creation and management of threads. This is the most basic way of working with multithreading.
C#
using System;
using System.Threading;
class Program {
static void PrintNumbers() {
for (int i = 1; i <= 5; i++) {
Console.WriteLine("Worker Thread: " + i);
Thread.Sleep(500); // Simulates work
}
}
static void Main() {
Thread t1 = new Thread(PrintNumbers);
t1.Start(); // Start the worker thread
for (int i = 1; i <= 5; i++) {
Console.WriteLine("Main Thread: " + i);
Thread.Sleep(500);
}
}
}
Output:
Worker Thread: 1
Main Thread: 1
Worker Thread: 2
Main Thread: 2
...
Explanation:
- Thread object is created with a target method.
- Start() begins execution in parallel with the main thread.
2. Using ParameterizedThreadStart
Sometimes threads need input data. C# provides the ParameterizedThreadStart delegate to pass parameters to threads.
C#
class Program {
static void PrintMessage(object msg) {
Console.WriteLine("Message: " + msg);
}
static void Main() {
Thread t = new Thread(PrintMessage);
t.Start("Hello from Thread");
}
}
Output:
Message: Hello from Thread
3. Using Lambda Expressions
Instead of separate methods, you can define thread logic inline.
C#
Thread t = new Thread(() => {
for (int i = 1; i <= 3; i++)
Console.WriteLine("Lambda Thread: " + i);
});
t.Start();
Output:
Lambda Thread: 1
Lambda Thread: 2
Lambda Thread: 3
Foreground and Background Threads
C# threads can run in two modes:
- Foreground Threads: Keep running until they finish, even if the main thread ends.
- Background Threads: Terminate when all foreground threads end.
C#
Thread t = new Thread(() => Console.WriteLine("Background thread running"));
t.IsBackground = true;
t.Start();
If the main thread ends before the background thread completes, the background thread is terminated.
Thread Synchronization
When multiple threads access and modify shared data, issues like race conditions can occur. This happens when two or more threads try to update the same variable at the same time, leading to inconsistent results.
To solve this, C# provides synchronization mechanisms. The most common is the lock statement, which ensures that only one thread can access a critical section of code at a time.
Example with Lock:
C#
class Counter {
private int count = 0;
private readonly object locker = new object();
public void Increment() {
lock (locker) {
count++;
Console.WriteLine("Count: " + count);
}
}
}
class Program {
static void Main() {
Counter counter = new Counter();
Thread t1 = new Thread(counter.Increment);
Thread t2 = new Thread(counter.Increment);
t1.Start();
t2.Start();
}
}
- The lock keyword ensures that only one thread at a time can enter the Increment() method.
- Without lock, both threads might try to update count simultaneously, causing incorrect results.
Thread Pooling
Creating and destroying threads repeatedly consumes resources. To optimize this, C# provides a Thread Pool, which is a collection of worker threads managed by the runtime. The pool reuses threads for multiple tasks, reducing overhead.
C#
using System.Threading;
class Program {
static void Print(object msg) {
Console.WriteLine("ThreadPool: " + msg);
}
static void Main() {
ThreadPool.QueueUserWorkItem(Print, "Task 1");
ThreadPool.QueueUserWorkItem(Print, "Task 2");
}
}
Output (order may vary):
ThreadPool: Task 1
ThreadPool: Task 2
Multithreading with Tasks
The Task Parallel Library (TPL) provides a modern and easier way to manage multithreading. It abstracts low-level thread management and is highly optimized.
C#
using System;
using System.Threading.Tasks;
class Program {
static void Main() {
Task t1 = Task.Run(() => {
for (int i = 1; i <= 5; i++)
Console.WriteLine("Task 1: " + i);
});
Task t2 = Task.Run(() => {
for (int i = 1; i <= 5; i++)
Console.WriteLine("Task 2: " + i);
});
Task.WaitAll(t1, t2); // Wait for both tasks
}
}
Output (interleaved):
Task 1: 1
Task 2: 1
Task 1: 2
Task 2: 2
...
Async and Await (High-Level Multithreading)
For I/O-bound operations (like file access, database calls, or API requests), C# provides async and await keywords. These make asynchronous code look like synchronous code, improving readability.
C#
using System;
using System.Threading.Tasks;
class Program {
static async Task FetchData() {
await Task.Delay(2000); // Simulates network call
Console.WriteLine("Data fetched!");
}
static async Task Main() {
Console.WriteLine("Fetching...");
await FetchData();
Console.WriteLine("Done!");
}
}
Output:
Fetching...
Data fetched!
Done!
Key Points
- A thread is a lightweight execution unit inside a process.
- Multiple threads can run in parallel to improve responsiveness and efficiency.
- Foreground threads keep running until completion, background threads end when the main thread finishes.
- Synchronization mechanisms like lock prevent race conditions when threads share data.
- Use ThreadPool, Task Parallel Library, or async/await for modern, efficient multithreading.
Explore
Introduction
Fundamentals
Control Statements
OOP Concepts
Methods
Arrays
ArrayList
String
Tuple
Indexers