Parallel LINQ in WinUI apps
Parallel LINQ (PLINQ) brings parallel execution semantics to familiar LINQ queries, letting you scale CPU-bound workloads across cores with minimal syntax changes.
Learn more
Overview
- Convert any
IEnumerable<T>pipeline into a parallel query withAsParallel(). - Control parallelism with
WithDegreeOfParallelismand fallback to sequential execution usingAsSequential()when needed. - Always marshal results back to the UI thread (with
DispatcherQueue) before touching WinUI controls.
Prerequisites
- .NET 6+ WinUI app targeting desktop scenarios
- Workloads that are CPU-bound or involve large in-memory datasets
- Awareness of thread safety for any shared state accessed inside query delegates
Filter and group packages in parallel
The following example filters installed packages for a user and groups them by publisher while evaluating the query in parallel.
var packages = PackageManager.FindPackagesForUser(sid.Value) .AsParallel() .WithDegreeOfParallelism(Environment.ProcessorCount - 1) .Where(pkg => pkg.Id.Publisher.Contains("Microsoft", StringComparison.OrdinalIgnoreCase)) .GroupBy(pkg => pkg.Id.Name) .OrderByDescending(group => group.Count()) .ToList();Avoid mutating captured variables inside PLINQ queries. Use local aggregates or thread-safe collections like
ConcurrentBag<T>if you need to accumulate results.
Cancellation and ordering
- Pass a
CancellationTokenviaWithCancellation(token)so users can stop long-running operations from the UI. - Use
AsOrdered()when the output needs to preserve the original sequence; otherwise, let PLINQ reorder elements for maximum throughput. - Combine
WithExecutionMode(ParallelExecutionMode.ForceParallelism)only after profiling—forcing parallel execution can degrade performance when datasets are small.
Performance checklist
- Measure before and after adding PLINQ; the overhead of partitioning can outweigh benefits for small collections.
- Prefer pure functions in query operators to keep work deterministic and debuggable.
- Batch UI updates by projecting results into DTOs, then update controls on the dispatcher thread to avoid cross-thread exceptions.