Summary
Add ExecuteAllInContext — an IContextManager extension that runs a list of async delegates inside one logical context scope, with per-task fork-join isolation and correct ExecutionContext/AsyncLocal flow.
Goal
Provide a safe, first-party replacement for a hand-rolled array of tasks + Task.WhenAll, so context metadata (trace/tenant/correlation) never leaks or is lost across parallel branches.
Problem
Bare Task.WhenAll does not guarantee that tasks launched in a loop keep the library's ambient context, especially under SynchronizationContext configurations. The current ContextManager only executes single actions, so there is no library-level fork-join primitive.
Scope
Internal Loop Reference
var tasks = delegates.Select(work => Task.Run(async () =>
{
using var scope = manager.BeginContext(snapshot);
try { await work(); }
catch (Exception ex) { Log(ex, snapshot.CorrelationId); }
}));
await Task.WhenAll(tasks);
Design Expectations
- Isolation by default: child mutations never leak to siblings or the parent thread.
- Deterministic lifetime tied to
Task.WhenAll duration.
- No global lock: the method owns no shared/global semaphore (resource control lives in the concurrency issue).
- Resource ownership stays with the caller.
- Child writes never propagate back unless an explicit aggregating adapter is used (Join discards).
Acceptance Criteria
Non-Goals
- No
MaxConcurrency in this issue — that is the separate ExecutionOptions concurrency issue.
- No ordered-result preservation, no partial-success (
TryEach), no graph orchestration here — separate issues.
- No blocking/
Parallel.ForEach — async-only.
Summary
Add
ExecuteAllInContext— anIContextManagerextension that runs a list of async delegates inside one logical context scope, with per-task fork-join isolation and correctExecutionContext/AsyncLocalflow.Goal
Provide a safe, first-party replacement for a hand-rolled array of tasks +
Task.WhenAll, so context metadata (trace/tenant/correlation) never leaks or is lost across parallel branches.Problem
Bare
Task.WhenAlldoes not guarantee that tasks launched in a loop keep the library's ambient context, especially underSynchronizationContextconfigurations. The currentContextManageronly executes single actions, so there is no library-level fork-join primitive.Scope
IContextManager<C>.ExecuteAllInContext(C ctx, params Func<Task>[] work)extension.manager.ExecuteInContext(snapshot, work)with a correctExecutionContext.Task.WhenAll; task scopes disposed deterministically when the batch completes.ContextLifecycleEventKind.Started/Completedaround each branch (per [Context]: add ActivityObserver for tracing context execution #11 event contract).Internal Loop Reference
Design Expectations
Task.WhenAllduration.Acceptance Criteria
Non-Goals
MaxConcurrencyin this issue — that is the separateExecutionOptionsconcurrency issue.TryEach), no graph orchestration here — separate issues.Parallel.ForEach— async-only.