<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Advanced C#
All lessons in this course

Lesson 49 of 60 15 minAdvanced

Tasks and parallel programming

Use every CPU core for heavy calculations: Task.Run, Parallel.For/ForEach, PLINQ, and when parallelism helps or hurts.

Your PC probably has 8 or more CPU cores, but a normal loop uses only one. For CPU-bound work (clash detection, mesh generation, structural checks on thousands of members) splitting the work across cores can make it several times faster.

Interactive

Using more CPU cores

12 independent jobs (say, checking 12 connections), each taking 1 second of pure calculation. Choose how many cores Parallel.For can use.

Cores:
Parallel.For(0, 12, i => Check(i)); // 4 cores
core 0
048
core 1
159
core 2
2610
core 3
3711
Total time3 s
Speed-up4×
Idle core-seconds0
4 cores finish in 3 s, a 4× speed-up. Real code gains a bit less: starting work items, sharing memory and any locks all cost time.

I/O-bound vs CPU-bound

I/O-boundCPU-bound
Time spentwaiting for disk, network, databasecalculating
Toolasync/await with …Async methodsTask.Run, Parallel, PLINQ
Exampledownload a file, query SQL Servertriangulate a mesh, check 50,000 connections

Task.Run: move heavy work off the UI thread

TaskRun.cs
private async void AnalyseButton_Click(object sender, RoutedEventArgs e){    AnalyseButton.IsEnabled = false;    var report = await Task.Run(() => RunClashDetection(model));   // runs on a pool thread    ResultsGrid.ItemsSource = report.Clashes;                        // back on the UI thread    AnalyseButton.IsEnabled = true;}

Parallel.For and Parallel.ForEach

Parallel.cs
double[] utilisation = new double[members.Length]; Parallel.For(0, members.Length, i =>{    utilisation[i] = CheckCapacity(members[i]);   // each index written by one thread only: safe}); Parallel.ForEach(drawings, new ParallelOptions { MaxDegreeOfParallelism = 4 }, d =>{    d.Render();});

Parallel.For splits the index range into chunks and runs them on several thread-pool threads, then waits until all are done.

PLINQ

Plinq.cs
var overloaded = members    .AsParallel()                         // the rest of the query runs on many cores    .Where(m => CheckCapacity(m) > 1.0)    .Select(m => m.Mark)    .ToList();

When parallel makes things worse

  • Tiny work per item: the overhead of scheduling is bigger than the work. Summing 1,000 numbers is faster on one thread.
  • Shared state: threads writing the same variable or list need locks, and waiting on locks kills the speed-up (next lesson).
  • Not thread-safe code: List<T>, Dictionary and many libraries must not be modified from several threads at once. Use ConcurrentBag, ConcurrentDictionary, or per-thread results.
  • Order matters: parallel loops finish items in unpredictable order.
  • UI objects: WPF controls may only be touched from the UI thread.
ThreadSafeTotal.cs
// ✗ data race: many threads doing total += …// ✓ give each thread its own subtotal, combine once at the enddouble total = 0;object gate = new();Parallel.ForEach(members,    () => 0.0,                                        // thread-local start value    (m, _, local) => local + m.WeightKg,              // no sharing inside the loop    local => { lock (gate) total += local; });        // combine once per threadConsole.WriteLine(total);
Quick check

Which tool fits a 20-second mesh calculation in a WPF app?

Quick check

Why can Parallel.For with list.Add(...) inside it crash or lose items?

Practice

Count the prime numbers below 2,000,000 with a normal loop and with Parallel.For using thread-local counters, and print both results and times with Stopwatch.

Show the solution
Solution.cs
using System.Diagnostics; static bool IsPrime(int n){    if (n < 2) return false;    for (int d = 2; (long)d * d <= n; d++) if (n % d == 0) return false;    return true;} const int N = 2_000_000;var sw = Stopwatch.StartNew();int seq = 0;for (int i = 0; i < N; i++) if (IsPrime(i)) seq++;Console.WriteLine($"Sequential: {seq} in {sw.ElapsedMilliseconds} ms"); sw.Restart();int par = 0;Parallel.For(0, N, () => 0,    (i, _, local) => IsPrime(i) ? local + 1 : local,    local => Interlocked.Add(ref par, local));Console.WriteLine($"Parallel:   {par} in {sw.ElapsedMilliseconds} ms");
Output
Sequential: 148933 in 610 ms
Parallel:   148933 in 105 ms

Remember

  • CPU-bound work benefits from multiple cores; I/O-bound work needs async instead.
  • Task.Run moves heavy work off the UI thread.
  • Parallel.For/ForEach and PLINQ split work across cores.
  • Avoid shared mutable state; measure the real speed-up.
0 of 60 lessons completed