C Adding context to Parallel.ForEach in ASP.NET
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When developers ask how to "add context" to Parallel.ForEach in ASP.NET, they usually mean one of two things: passing shared request data into each iteration, or trying to access request-specific objects such as HttpContext. The safe pattern is to copy the data you need into an explicit context object before the loop and avoid relying on ambient ASP.NET request state inside parallel workers.
Do Not Depend On Request Ambient State
Parallel.ForEach runs work on thread-pool threads. In ASP.NET, request-scoped state such as HttpContext.Current is not something you should casually rely on inside those parallel iterations.
Even if it seems to work in one environment, it creates fragile code because the loop body is no longer a simple function of its inputs. It depends on hidden request infrastructure instead.
The better design is:
- capture what you need from the request up front
- package it into an immutable object
- pass that object into the loop body
Pass An Explicit Context Object
Example:
The context is explicit, immutable, and safe to share across threads because it contains only read-only data.
That is usually all you need.
Use Thread-Local State When Each Worker Needs Its Own Scratch Data
Parallel.ForEach also supports local state for each worker. This is useful when every thread needs its own accumulator or expensive helper object.
This pattern is good for thread-local aggregation, but it is not a replacement for ASP.NET request context. It is just a way to manage per-worker state efficiently.
Prefer Async I/O For Request Work
There is an even bigger ASP.NET-specific point: Parallel.ForEach is usually appropriate for CPU-bound work, not I/O-bound request fan-out.
If your loop body is making HTTP calls, database queries, or file access, async code is usually the better model. Spinning up parallel blocking work inside an ASP.NET request can waste threads and reduce scalability.
In those cases, prefer:
- '
Task.WhenAll' - async database APIs
- async HTTP clients
Reserve Parallel.ForEach for real parallel CPU work such as transformations, hashing, or independent calculations.
Limit Parallelism Deliberately
If you do use Parallel.ForEach in a web application, control the concurrency instead of assuming "more threads is better."
That keeps the loop from competing too aggressively with the rest of the application.
Thread Safety Still Matters
Passing context safely does not make shared mutable objects safe. If multiple iterations write to the same collection or service, use thread-safe structures or redesign the flow.
Good shared choices include:
- immutable data
- '
ConcurrentDictionary' - '
ConcurrentBag' - per-thread local state with a merge step
Bad choices include mutable lists and request-scoped objects being modified from many iterations at once.
Common Pitfalls
The biggest mistake is reading HttpContext or other ambient request state directly inside the loop body. Capture only the values you need before the parallel section starts.
Another mistake is using Parallel.ForEach for I/O-heavy request work. In ASP.NET, async I/O scales better than blocking parallel loops for that scenario.
People also forget that shared collections are not magically safe just because the context object is immutable. Shared writes still need thread-safe handling.
Finally, do not assume maximum parallelism should be unlimited. In web apps, aggressive parallel fan-out can hurt overall throughput.
Summary
- In ASP.NET, pass explicit immutable context data into
Parallel.ForEachinstead of depending on ambient request state. - Use thread-local loop state only for per-worker scratch data or aggregation.
- '
Parallel.ForEachis best for CPU-bound work, not request-time I/O fan-out.' - Control concurrency with
ParallelOptionsand use thread-safe result collection. - Safe context passing does not remove the need for normal thread-safety discipline.

