Task Parallelism vs Multithreading vs Parallel Class in C#

 When you're dealing with concurrent or parallel processing, you have multiple options in C#. Each is suited for different use cases:

ConceptDescription
MultithreadingManual control using Thread class.
Task ParallelismAsynchronous processing using Task and async/await.
Parallel ClassExecutes 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

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 on await)
  • ✅ 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

FeatureThreadTask / asyncParallel.ForEach
Abstraction LevelLowHighMedium
Use CaseLegacy, fine controlI/O-bound, scalable workCPU-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 with async/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!

Vikash Chauhan

C# & .NET experienced Software Engineer with a demonstrated history of working in the computer software industry.

Post a Comment

Previous Post Next Post

Contact Form