What are the fundamental concepts of threading in .NET?
15 minadvanced.NETthreadingconcurrencyTask
Quick Answer
Threading enables concurrent operations using Thread class, ThreadPool, or Task. Key concepts include thread synchronization (lock, Monitor, Mutex), thread safety, race conditions, and deadlocks. Use Task instead of Thread for most scenarios, avoid shared mutable state, use appropriate synchronization mechanisms, and prefer async/await for I/O operations.
Detailed Answer
Threading allows your application to perform multiple operations concurrently, improving responsiveness and utilizing multiple CPU cores effectively.
Basic Threading Concepts:
Thread vs Process:
// Process: Complete application with its own memory space
// Thread: Unit of execution within a process
// A process can have multiple threads
// Creating a new thread
Thread newThread = new Thread(() =>
{
Console.WriteLine("Running on background thread");
Thread.Sleep(2000);
Console.WriteLine("Background thread completed");
});
newThread.Start();
Console.WriteLine("Main thread continues...");
Thread Class:
public class ThreadExample
{
public static void Main()
{
// Create and start a thread
Thread workerThread = new Thread(DoWork);
workerThread.Start();
// Main thread continues
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Main thread: {i}");
Thread.Sleep(500);
}
// Wait for worker thread to complete
workerThread.Join();
Console.WriteLine("All threads completed");
}
static void DoWork()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Worker thread: {i}");
Thread.Sleep(300);
}
}
}
ThreadPool:
// ThreadPool manages a pool of worker threads
// More efficient than creating new threads manually
// Automatically manages thread lifecycle
public class ThreadPoolExample
{
public static void Main()
{
// Queue work to ThreadPool
ThreadPool.QueueUserWorkItem(DoWork, "Task 1");
ThreadPool.QueueUserWorkItem(DoWork, "Task 2");
ThreadPool.QueueUserWorkItem(DoWork, "Task 3");
Console.WriteLine("Main thread continues...");
Thread.Sleep(3000); // Wait for tasks to complete
}
static void DoWork(object state)
{
string taskName = (string)state;
Console.WriteLine($"ThreadPool thread executing: {taskName}");
Thread.Sleep(1000);
Console.WriteLine($"Completed: {taskName}");
}
}
Race Conditions:
// Race condition example
public class RaceConditionExample
{
private static int counter = 0;
public static void Main()
{
// Start multiple threads that modify shared data
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(IncrementCounter);
threads[i].Start();
}
// Wait for all threads
foreach (Thread thread in threads)
{
thread.Join();
}
Console.WriteLine($"Final counter value: {counter}");
// Expected: 5000, Actual: varies due to race condition
}
static void IncrementCounter()
{
for (int i = 0; i < 1000; i++)
{
counter++; // Race condition here!
}
}
}
Thread Synchronization:
1. Lock Statement:
public class SynchronizedExample
{
private static int counter = 0;
private static readonly object lockObject = new object();
public static void Main()
{
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(IncrementCounterSafely);
threads[i].Start();
}
foreach (Thread thread in threads)
{
thread.Join();
}
Console.WriteLine($"Final counter value: {counter}"); // Always 5000
}
static void IncrementCounterSafely()
{
for (int i = 0; i < 1000; i++)
{
lock (lockObject) // Thread-safe increment
{
counter++;
}
}
}
}
2. Monitor Class:
public class MonitorExample
{
private static readonly object lockObject = new object();
public static void Main()
{
Thread thread1 = new Thread(DoWorkWithMonitor);
Thread thread2 = new Thread(DoWorkWithMonitor);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
}
static void DoWorkWithMonitor()
{
Monitor.Enter(lockObject);
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} acquired lock");
Thread.Sleep(2000);
}
finally
{
Monitor.Exit(lockObject);
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} released lock");
}
}
}
3. Mutex:
public class MutexExample
{
private static Mutex mutex = new Mutex();
public static void Main()
{
Thread[] threads = new Thread[3];
for (int i = 0; i < 3; i++)
{
threads[i] = new Thread(DoWorkWithMutex);
threads[i].Start();
}
foreach (Thread thread in threads)
{
thread.Join();
}
}
static void DoWorkWithMutex()
{
mutex.WaitOne(); // Acquire mutex
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} in critical section");
Thread.Sleep(1000);
}
finally
{
mutex.ReleaseMutex(); // Release mutex
}
}
}
Thread vs Task:
// Thread - lower level, more control
Thread thread = new Thread(() =>
{
Console.WriteLine("Thread-based work");
});
thread.Start();
// Task - higher level, better for most scenarios
Task task = Task.Run(() =>
{
Console.WriteLine("Task-based work");
});
// Task with return value
Task<int> taskWithResult = Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});
int result = await taskWithResult;
Console.WriteLine($"Result: {result}");
Thread Safety:
public class ThreadSafeCounter
{
private int _count = 0;
private readonly object _lock = new object();
public int Count
{
get
{
lock (_lock)
{
return _count;
}
}
}
public void Increment()
{
lock (_lock)
{
_count++;
}
}
public void Decrement()
{
lock (_lock)
{
_count--;
}
}
}
Best Practices:
- Use Task instead of Thread for most scenarios
- Avoid shared mutable state when possible
- Use appropriate synchronization mechanisms
- Don't lock on public objects or types
- Keep critical sections short to avoid blocking
- Use thread-safe collections when available
- Avoid Thread.Sleep in production code
- Use async/await for I/O operations
Common Threading Issues:
- Race conditions - multiple threads accessing shared data
- Deadlocks - threads waiting for each other indefinitely
- Starvation - some threads never get CPU time
- Context switching overhead - too many threads can hurt performance