Beyond async/ for I/O, .NET provides the for — running computations across multiple cores. The key distinction: / is for I/O concurrency (non-blocking waits), while , , and PLINQ are for parallelizing CPU-intensive work.
Beyond async/ for I/O, .NET provides the for — running computations across multiple cores. The key distinction: / is for I/O concurrency (non-blocking waits), while , , and PLINQ are for parallelizing CPU-intensive work.
awaitasyncawaitTask.RunParallelasync/await → I/O-bound concurrency. Frees the thread during waits (DB, network).
Does NOT add CPU parallelism.
Parallelism → CPU-bound work spread across MULTIPLE CORES (computation, processing).
Use Task.Run, Parallel.For/ForEach, PLINQ.
// run a CPU-intensive computation on a thread pool thread (don't block the caller)
int result = await Task.Run(() => ExpensiveComputation());
// run multiple in parallel and combine
var tasks = items.Select(item => Task.Run(() => Process(item)));
var results = await Task.WhenAll(tasks); // wait for all
Task.Run schedules work on a thread pool thread — use it to parallelize CPU-bound computation (NOT for I/O, where async/await already suffices without extra threads).
// Parallel.For/ForEach — process a collection across cores
Parallel.ForEach(items, item => Process(item)); // automatically uses multiple cores
Parallel.For(0, 1000, i => Compute(i));
// PLINQ — parallel LINQ
var results = data.AsParallel().Where(x => IsValid(x)).Select(Transform).ToList();
Parallel.For/ForEach and PLINQ (.AsParallel()) automatically distribute work over cores — great for CPU-heavy processing of independent items. (Only worth it for genuinely CPU-bound, sizeable work — there's overhead.)
// parallel code accessing SHARED state needs synchronization or thread-safe types
lock (_lock) { _counter++; } // lock for mutual exclusion
Interlocked.Increment(ref _counter); // lock-free atomic
var dict = new ConcurrentDictionary<string, int>(); // thread-safe collection
Understanding Tasks and parallelism beyond async/await is important senior-level knowledge for building performant C# applications, and the crucial distinction between I/O concurrency and CPU parallelism is the key insight — a common point of confusion. async/await handles I/O-bound concurrency (freeing threads during waits) but does not add CPU parallelism, whereas CPU-bound work (computations, data processing) benefits from actual parallelism across cores via Task.Run (offloading CPU work to thread pool threads), Parallel.For/ForEach, and PLINQ (.AsParallel()).
Knowing which tool fits — async/await for I/O, the TPL parallelism tools for CPU-bound work — is essential for effective performance optimization, since using the wrong one (e.g. expecting async to speed up computation, or spinning up threads for I/O that async handles efficiently) is ineffective.
Equally important is understanding thread safety: parallel code accessing shared state needs synchronization (lock, Interlocked for atomic operations, or thread-safe collections like ConcurrentDictionary) to avoid race conditions — a critical correctness concern in parallel code.
Understanding the I/O-vs-CPU distinction, the parallelism tools (Task.Run, Parallel, PLINQ), and thread-safety requirements is valuable for writing correct, performant concurrent and parallel C#.
Since modern applications often need both I/O concurrency and CPU parallelism, and since choosing the right approach and handling thread safety correctly are what make parallel code effective and correct, this is important, frequently-relevant senior knowledge for performance-sensitive C# development.