Skip to content

[Context]: add ExecuteAllInContext fork-join batch runner #16

Open

Description

@rian-be

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

  • IContextManager<C>.ExecuteAllInContext(C ctx, params Func<Task>[] work) extension.
  • Per-delegate snapshotting: each task starts from its own clone of the entering context (Isolation by default, No Ambient Write).
  • Launches each delegate through manager.ExecuteInContext(snapshot, work) with a correct ExecutionContext.
  • Waits via Task.WhenAll; task scopes disposed deterministically when the batch completes.
  • Emit ContextLifecycleEventKind.Started/Completed around each branch (per [Context]: add ActivityObserver for tracing context execution #11 event contract).

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

  • Context flows to every branch; each branch has an isolated snapshot.
  • Child context changes do not affect siblings or the caller afterward.
  • Scopes are disposed once the batch completes.
  • Events emitted per task and testable via the lifecycle observer.
  • No behavior change for a single delegate.

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.

Activity

  1. added
    enhancementNew feature or request
    concurrencyParallel / batch / async execution
    extensionExtension behaviors / helpers
    on Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    concurrencyParallel / batch / async executionenhancementNew feature or requestextensionExtension behaviors / helpers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions