Multithreading
Delegate
A delegate is a type that references methods with a specific parameter list and return
type.
Ex:
public delegate int MyDelegate(int x, int y);
Example
//delegate
using System;
public class Program
public delegate int CompareDelegate(int a, int b);
public static int GetMax(int a, int b)
return (a > b) ? a : b;
public static int GetMin(int a, int b)
return (a < b) ? a : b;
public static void Main()
CompareDelegate maxDelegate = new CompareDelegate(GetMax);
CompareDelegate minDelegate = new CompareDelegate(GetMin);
int x = 20, y = 35;
Console.WriteLine("Max of {0} and {1} is: {2}", x, y, maxDelegate(x, y));
Console.WriteLine("Min of {0} and {1} is: {2}", x, y, minDelegate(x, y));
}
Multithreading
Refers to the ability of a program to execute multiple threads concurrently, where each
thread runs a part of the program's logic.
A thread is the smallest unit of a CPU's execution, and multithreading allows for the
parallel execution of tasks, improving performance, especially on multi-core processors.
Applications
Web Servers
Handle multiple client requests simultaneously (e.g., Apache, Nginx).
Each request is processed in a separate thread to ensure faster and concurrent
processing.
GUI Applications
Keeps the user interface responsive by running heavy background tasks (e.g., file
loading, data processing) on separate threads.
Gaming
Separate threads for rendering graphics, processing game logic, and handling user
input to maintain smooth gameplay.
Creating and Managing Threads
Threads can be created in C# by using the Thread class in the System.Threading
namespace.
To create a thread, you define a method that will run on that thread, then start the
thread using the Thread.Start() method.
Example
using System;
using System.Threading;
class Program
static void Main()
Thread thread1 = new Thread(PrintNumbers);
thread1.Start(); // Starts the new thread
static void PrintNumbers()
for (int i = 1; i <= 5; i++)
Console.WriteLine(i);
Thread.Sleep(1000); // Simulate work by sleeping
}
Thread Lifecycle
A thread goes through several stages:
New: When the thread is created but not yet started.
Runnable: When the thread is ready to run.
Running: When the thread is actively executing.
Blocked: When the thread is waiting for a resource (e.g., waiting to acquire a lock).
Terminated: When the thread finishes its task or is stopped.
Thread Synchronization
When multiple threads access shared resources, it’s important to ensure thread safety,
which can be done using synchronization techniques.
Without proper synchronization, it could lead to issues like race conditions or data
corruption.
Race conditions: Threads race to update a shared variable, leading to unpredictable
results.
Deadlocks: Two threads wait for each other’s resources, and both get stuck.
Data inconsistency: Shared data becomes incorrect due to unsynchronized access.
Common Synchronization Techniques:
Locks (lock keyword): The lock keyword ensures that only one thread can access a
block of code at a time.
Monitor: Another way to synchronize access to resources is using the Monitor class,
which provides more advanced synchronization techniques.
Mutex: A Mutex is used to synchronize threads across different processes. It's like a lock
but can be used for inter-process synchronization.
Example :Lock
class Program
private static int counter = 0;
private static object lockObject = new object();
static void Main()
Thread t1 = new Thread(IncrementCounter);
Thread t2 = new Thread(IncrementCounter);
t1.Start();
t2.Start();
static void IncrementCounter()
lock (lockObject)
for (int i = 0; i < 1000; i++)
counter++; // Increment counter safely
Console.WriteLine(counter);
}
Example-Monitor
Monitor.Enter(lockObject);
// Code block
Monitor.Exit(lockObject);
Example :Mutex
Mutex mutex = new Mutex();
mutex.WaitOne(); // Acquiring the mutex
// Code block
mutex.ReleaseMutex(); // Releasing the mutex
Thread Pooling
Thread pooling is a technique where a pool (collection) of pre-created threads is
maintained to execute tasks, rather than creating and destroying threads for each task
individually.
Instead of creating a new thread every time a task needs to run, you reuse existing
threads from the pool.
The thread pool automatically manages thread lifecycle (reuse, scheduling, etc.).
The .NET Framework provides a ThreadPool class to manage a pool of worker threads.
This reduces the overhead of creating and destroying threads each time a task needs to
run.
Thread Pooling Ex
using System;
using System.Threading;
class Program
static void PrintNumbers(object state)
for (int i = 1; i <= 5; i++)
Console.WriteLine(i); // Print number
Thread.Sleep(1000); // Sleep for 1 second
static void Main()
ThreadPool.QueueUserWorkItem(PrintNumbers);
Console.WriteLine("Main thread continues...");
Console.ReadLine(); // Keep console window open until user presses a key
}
Advantages of Thread Pool:
Threads are reused, so you don’t have to create new ones.
It helps reduce the overhead of thread management.
It’s suitable for short-lived tasks or tasks that can run in the background.
Deadlock and Race Conditions
Race Condition: This occurs when multiple threads access shared resources
simultaneously, and the final result depends on the order of execution.
Deadlock: A situation where two or more threads are blocked indefinitely, each waiting
for the other to release a resource.
To avoid these issues:
Use synchronization primitives (e.g., lock, Monitor).
Avoid locking on multiple resources at once (this can cause deadlock).
Use async/await for I/O-bound tasks to avoid blocking the thread.
Asychronous Delegate
An asynchronous delegate allows you to call a method asynchronously using delegates,
The method runs on a separate thread without blocking the main thread.
This is particularly useful in UI applications or any scenario where you want to maintain
responsiveness.
Calling a Delegate Asynchronously
You can use BeginInvoke and EndInvoke to call a delegate asynchronously (classic
pattern before async/await was introduced).
Ex
using System;
public class Program
public delegate int AddDelegate(int a, int b);
public static void Main()
AddDelegate del = Add;
// Asynchronous call
IAsyncResult result = del.BeginInvoke(10, 20, null, null);
// Do something else while waiting...
// Get the result
int sum = del.EndInvoke(result);
Console.WriteLine($"Result: {sum}");
public static int Add(int x, int y)
System.Threading.Thread.Sleep(2000); // Simulate work
return x + y;
}
Modern Alternative: async and Task
Since C# 5.0, it's recommended to use async and await with Task instead of
BeginInvoke/EndInvoke.
public static async Task Main()
int result = await Task.Run(() => Add(10, 20));
Console.WriteLine($"Result: {result}");
}
Timers in Multithreading
Execute code periodically or after a delay, often on a separate thread.
Common Timer Classes in C#
System.Timers.Timer (Multithreaded-safe)
Executes an event on a ThreadPool thread at regular intervals.
System.Threading.Timer
Executes a callback at a specified interval, also on a ThreadPool thread.
System.Windows.Forms.Timer
Designed for Windows Forms UI apps. Runs on the UI thread, not suitable for
long-running tasks.
DispatcherTimer (WPF)
Runs on the UI thread in WPF, good for updating UI elements.
Ex:System.Timers.Timer (Multithreaded-safe)
using System;
using System.Timers;
class Program
static void Main()
System.Timers.Timer timer = new System.Timers.Timer(2000);
timer.Elapsed += (sender, e) =>
Console.WriteLine("Tick: " + DateTime.Now);
};
timer.AutoReset = true;
timer.Enabled = true;
Console.WriteLine("Press Enter to stop the timer.");
Console.ReadLine(); // Wait for user input to exit the application
}
Ex:System.Threading.Timer
using System.Threading;
Timer timer = new Timer((e) =>
Console.WriteLine("Tick at: " + DateTime.Now);
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));