When you're dealing with concurrent or parallel processing, you have multiple options in C#. Each is suited for different use cases:
Concept | Description |
---|---|
Multithreading | Manual control using Thread class. |
Task Parallelism | Asynchronous processing using Task and async/await . |
Parallel Class | Executes CPU-bound tasks in parallel, leveraging multiple CPU cores. |
🧠 Real-Life Scenario: PDF Thumbnail Generator
You want to generate thumbnails for multiple PDF files in parallel. Each operation is independent, and performance improves by processing them concurrently.
✅ 1. Using Multithreading
Good when you need low-level control (less common in modern apps)
public void ProcessPDFsWithThreads(List<string> pdfFiles)
{
foreach (var file in pdfFiles)
{
Thread thread = new Thread(() => ProcessSinglePDF(file));
thread.Start();
}
}
private void ProcessSinglePDF(string filePath)
{
Console.WriteLine($"[Thread] Started: {filePath}");
Thread.Sleep(2000); // Simulate processing
Console.WriteLine($"[Thread] Done: {filePath}");
}
- ❌ Difficult to manage thread completion
- ❌ Not scalable for large workloads
✅ 2. Using Task Parallelism (Recommended for most apps)
High-level and scalable. Uses ThreadPool behind the scenes.
public async Task ProcessPDFsAsync(List<string> pdfFiles)
{
var tasks = pdfFiles.Select(file =>
Task.Run(() => ProcessSinglePDF(file)));
await Task.WhenAll(tasks);
}
private void ProcessSinglePDF(string filePath)
{
Console.WriteLine($"[Task] Started: {filePath}");
Thread.Sleep(2000); // Simulate processing
Console.WriteLine($"[Task] Done: {filePath}");
}
- ✅ Built-in task management
- ✅ Better error handling (
try-catch
onawait
) - ✅ Scales well for I/O or mixed workloads
✅ 3. Using Parallel.ForEach
— Multiple CPU Cores
Best for CPU-bound tasks (e.g., image processing, data crunching). Uses all available cores efficiently.
public void ProcessPDFsUsingParallel(List<string> pdfFiles)
{
Parallel.ForEach(pdfFiles, file =>
{
Console.WriteLine($"[Parallel] Started: {file} on Thread {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000); // Simulate CPU-intensive processing
Console.WriteLine($"[Parallel] Done: {file}");
});
}
- ✅ Utilizes multiple CPU cores
- ✅ Very fast for large collections of CPU-bound work
- ❌ Not
await
-able (not suitable for async I/O tasks)
✅ Summary Comparison
Feature | Thread | Task / async | Parallel.ForEach |
---|---|---|---|
Abstraction Level | Low | High | Medium |
Use Case | Legacy, fine control | I/O-bound, scalable work | CPU-bound work, max throughput |
Async Support | ❌ Manual handling | ✅ await support | ❌ Not awaitable |
Thread Reuse | ❌ New threads | ✅ ThreadPool threads | ✅ ThreadPool + multi-core |
Ease of Use | ❌ Verbose | ✅ Simple | ✅ Simple |
🛠 Real-World Suggestion
- Use
Task
withasync/await
for web APIs, I/O, and background work. - Use
Parallel.ForEach
for CPU-heavy operations like data processing, image rendering, or simulations. - Avoid manual threads unless necessary.
Great question! In C#, both Task.WaitAll
and Task.WhenAll
are used to wait for multiple tasks to complete, but they differ in how they wait and their usage patterns.
✅ Task.WaitAll
vs Task.WhenAll
— Key Differences
Feature | Task.WaitAll |
Task.WhenAll |
---|---|---|
Type | Synchronous | Asynchronous |
Return Type | void |
Task or Task<T[]> |
Blocking | ✅ Blocks the calling thread | ❌ Non-blocking (awaitable) |
Exception Handling | Aggregates exceptions directly | Exceptions can be caught via try /await |
Best Used In | Console apps, synchronous contexts | Async methods, UI/web apps |
🔍 Example
🔹 Task.WaitAll
(Synchronous, Blocking)
public void WaitAllExample()
{
Task t1 = Task.Run(() => DoWork("Task1"));
Task t2 = Task.Run(() => DoWork("Task2"));
Task.WaitAll(t1, t2); // Blocks here until both finish
Console.WriteLine("All tasks completed (WaitAll)");
}
⚠️ Blocks the current thread. Not recommended for UI or ASP.NET apps.
🔹 Task.WhenAll
(Asynchronous, Non-blocking)
public async Task WhenAllExample()
{
Task t1 = Task.Run(() => DoWork("Task1"));
Task t2 = Task.Run(() => DoWork("Task2"));
await Task.WhenAll(t1, t2); // Asynchronously waits
Console.WriteLine("All tasks completed (WhenAll)");
}
✅ Ideal for
async/await
workflows — UI stays responsive.
🔥 Exception Handling Difference
With WaitAll
try
{
Task t1 = Task.Run(() => throw new Exception("Error in Task1"));
Task t2 = Task.Run(() => throw new Exception("Error in Task2"));
Task.WaitAll(t1, t2);
}
catch (AggregateException ex)
{
foreach (var inner in ex.InnerExceptions)
Console.WriteLine(inner.Message);
}
With WhenAll
try
{
Task t1 = Task.Run(() => throw new Exception("Error in Task1"));
Task t2 = Task.Run(() => throw new Exception("Error in Task2"));
await Task.WhenAll(t1, t2);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // Still gets AggregateException if multiple errors
}
✅ Summary
Use This... | When... |
---|---|
Task.WaitAll() |
You need to block synchronously (e.g., console apps or specific background work). |
Task.WhenAll() |
You’re working in an async context, such as ASP.NET, UI apps, or modern service code. |
Let me know if you want to see this in a working console or ASP.NET example!