Found in the pre-4.0 audit (follow-up to #360/#361).
AsyncEnumerableExtensions.ProcessInParallel<T, TOutput>(items, selector, maxConcurrency: null, ...) — the unbounded path has no try/finally around enumeration, unlike the equivalent processor class. If MoveNextAsync throws or the token cancels mid-enumeration, tasks already started keep running fire-and-forget and their failures surface only as UnobservedTaskException at GC.
Failure scenario: source yields 50 items then throws; 50 selector tasks are in flight → the extension rethrows the enumeration error immediately, the 50 tasks are abandoned, and any of their exceptions hit TaskScheduler.UnobservedTaskException later.
Same fluent shape via the processor class (SelectAsync(...).ProcessInParallel()) produces a structured wait in its finally; the extension and processor paths should behave identically.
Proposal: wrap enumeration in try/finally that awaits (and observes) started tasks, mirroring AsyncEnumerableParallelProcessor — while coordinating with the exception-masking fix tracked separately.
Found in the pre-4.0 audit (follow-up to #360/#361).
AsyncEnumerableExtensions.ProcessInParallel<T, TOutput>(items, selector, maxConcurrency: null, ...)— the unbounded path has no try/finally around enumeration, unlike the equivalent processor class. IfMoveNextAsyncthrows or the token cancels mid-enumeration, tasks already started keep running fire-and-forget and their failures surface only asUnobservedTaskExceptionat GC.Failure scenario: source yields 50 items then throws; 50 selector tasks are in flight → the extension rethrows the enumeration error immediately, the 50 tasks are abandoned, and any of their exceptions hit
TaskScheduler.UnobservedTaskExceptionlater.Same fluent shape via the processor class (
SelectAsync(...).ProcessInParallel()) produces a structured wait in itsfinally; the extension and processor paths should behave identically.Proposal: wrap enumeration in try/finally that awaits (and observes) started tasks, mirroring
AsyncEnumerableParallelProcessor— while coordinating with the exception-masking fix tracked separately.