Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Spyros Ponaris
Spyros Ponaris

Posted on • Edited on

     

🚀 Parallel.ForEachAsync vs Task.Run in C#: A Beginner’s Guide

🚀 Parallel.ForEachAsync vs Task.Run in C#: A Beginner’s Guide

When you start writing C# code that needs to do things at the same time, two methods often come up:

  • Task.Run
  • Parallel.ForEachAsync

At first, they both look like “magic ways to run things in parallel.”
But actually, they are built for different jobs. Let’s explore them step by step.

  1. First things first: what do they do?

🟢Task.Run

Think of Task.Run as saying:
👉 “Run this one heavy piece of work on a background thread so my app doesn’t freeze.”

It’s great for CPU-bound work like:

  • resizing an image
  • encrypting a file
  • running a long calculation

🟢 Parallel.ForEachAsync

This one is more like:
👉 “I have a list of things to process. Please handle them in parallel, but don’t overload me—keep it under control.”

It’s built for async I/O work like:

  • calling multiple APIs
  • downloading files
  • querying a database

It also lets you set a limit (MaxDegreeOfParallelism) so you don’t spam the server or your own machine.
And yes, it supports CancellationToken out of the box.

  1. 📜 A quick history lesson

When Parallel.ForEach was first introduced in .NET 4 (2010), it was designed for CPU-bound, in-memory operations — things like processing arrays, crunching numbers, or running loops over data in memory.

Back then, calling web services inside Parallel.ForEach was discouraged because:

  • Each request would block a thread while waiting.
  • This caused thread-pool starvation and poor scalability.

That’s why the advice was: “Parallel.ForEach is only for CPU-bound in-memory work.”

🔹 Fast forward to .NET 6 (2021): the .NET team introduced Parallel.ForEachAsync.

Why?

  • To bring the power of parallel loops to async/await workloads.
  • To make it safe and efficient to run I/O-bound tasks (like HTTP requests, DB queries, or file I/O) in parallel.
  • To give developers built-in throttling with MaxDegreeOfParallelism, so you don’t need to hand-roll SemaphoreSlim loops.
  • So if you’ve heard that “Parallel is only for in-memory data” — that’s true for the old synchronous API, but with .NET 6+, Parallel.ForEachAsync is the recommended way to handle async I/O in parallel.
  1. Examples

Example A: Calling APIs in parallel (I/O-bound)
Here’s how you fetch multiple web pages at the same time:

varurls=new[]{"https://api.site.com/page/1","https://api.site.com/page/2","https://api.site.com/page/3"};awaitParallel.ForEachAsync(urls,newParallelOptions{MaxDegreeOfParallelism=3,CancellationToken=cancellationToken},async(url,ct)=>{varresponse=awaithttpClient.GetStringAsync(url,ct);Console.WriteLine($"{url} =>{response.Length} chars");});
Enter fullscreen modeExit fullscreen mode

👉 At most 3 requests run at the same time.
👉 Each call respects cancellation.
👉 No threads are blocked while waiting on I/O.

📌 Alternative (simpler, no throttling):

awaitTask.WhenAll(urls.Select(u=>httpClient.GetStringAsync(u,cancellationToken)));
Enter fullscreen modeExit fullscreen mode

Example B: Heavy image processing (CPU-bound)

Now let’s say you’re processing a huge image. This is CPU work, not I/O.

varresult=awaitTask.Run(()=>HeavyImageProcessing(inputImage),cancellationToken);Console.WriteLine($"Processed{result.Count} pixels");
Enter fullscreen modeExit fullscreen mode

👉 Here, Task.Run makes sure the CPU-heavy task doesn’t block your main thread (like an ASP.NET request or UI).
**
Example C: CPU-heavy loop (many items)**

If you have lots of CPU work to spread across cores, use Parallel.ForEach:

Parallel.ForEach(files,newParallelOptions{MaxDegreeOfParallelism=Environment.ProcessorCount},file=>{ProcessFile(file);// CPU-heavy});
Enter fullscreen modeExit fullscreen mode

👉 This keeps CPU cores busy without tying up async infrastructure.

  1. Mistakes to avoid

❌ Wrapping async calls in Task.Run

// Bad: don’t wrap async I/O

awaitTask.Run(()=>httpClient.GetStringAsync(url));
Enter fullscreen modeExit fullscreen mode

This just wastes a thread. Use await httpClient.GetStringAsync(url) directly, or batch with Task.WhenAll/Parallel.ForEachAsync.

❌ Using Parallel.ForEachAsync for CPU-heavy loops

// Bad: CPU-heavy work inside Parallel.ForEachAsync

awaitParallel.ForEachAsync(files,async(file,ct)=>{vardata=ProcessFile(file);// CPU-bound});
Enter fullscreen modeExit fullscreen mode

This ties up thread-pool threads and isn’t optimal.
✅ Use Parallel.ForEach/PLINQ (CPU) or throttle multiple Task.Runs.

  1. Rule of Thumb

📝 Cheat sheet:

  • Parallel.ForEachAsync → many async I/O tasks (API calls, DB queries, file downloads) with throttling.
  • Parallel.ForEach/PLINQ → bulk CPU-bound loops.
  • Task.Run → a CPU task (or a few, but throttle if many).
  • Both → accept a CancellationToken, so you can stop them if needed.

Think of it like this:

🔹 Parallel.ForEachAsync = “lots of async things at once”
🔹 Parallel.ForEach = “lots of CPU work in parallel”
🔹 Task.Run = “this one CPU-heavy thing, off the main thread”
🔹 CancellationToken = “and I can pull the plug anytime”

  1. Takeaway

Don’t mix them up:

If you’re waiting on the network, useParallel.ForEachAsync (orTask.WhenAll if throttling isn’t needed).

If you’re burning the CPU across multiple items, use Parallel.ForEach or PLINQ.

If you need to push one heavy CPU job off the caller thread, use Task.Run.

Both make your code faster and more responsive when used in the right place.

👉 Next step: Try rewriting one of your loops. Ask yourself: is this I/O or CPU? That’s how you’ll know which tool to grab.

📚 References

Microsoft Docs – Parallel.ForEachAsync

Stephen Toub – Introducing Parallel.ForEachAsync in .NET 6

Task.Run documentation

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
antinonsense6 profile image
antinonsense
  • Joined
• Edited on• Edited

"This eats threads without benefit." This is not correct and I'm not sure what you trying trying to get at.

CollapseExpand
 
stevsharp profile image
Spyros Ponaris
Tech Lead | Senior Software Engineer | .NET Enthusiast (BSc, MSc, MCP, MCSD)Contact Information:https://www.linkedin.com/in/spyros-ponaris-913a6937/
  • Email
  • Location
    Athens
  • Education
    BSc University of Sunderland- MSc University of Greenwich
  • Pronouns
    Tech Lead and Senior Software Engineer with a strong focus on .NET technologies.
  • Work
    Tech Lead | Senior Software Engineer |
  • Joined

You’re right my wording wasn’t precise. What I meant is that using Task.Run inside a loop for I/O-bound operations can lead to unnecessary thread usage compared to Parallel.ForEachAsync, which schedules more efficiently. I’ll rephrase this part to avoid confusion.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Tech Lead | Senior Software Engineer | .NET Enthusiast (BSc, MSc, MCP, MCSD)Contact Information:https://www.linkedin.com/in/spyros-ponaris-913a6937/
  • Location
    Athens
  • Education
    BSc University of Sunderland- MSc University of Greenwich
  • Pronouns
    Tech Lead and Senior Software Engineer with a strong focus on .NET technologies.
  • Work
    Tech Lead | Senior Software Engineer |
  • Joined

More fromSpyros Ponaris

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp