dotnet/reactive · error · AggregateException

One or more errors occurred.

Error message

One or more errors occurred.

What it means

Merge concurrently iterates multiple source async sequences into one. If any source fails, Merge waits for all sources to finish cleanup, then throws an AggregateException containing every exception observed, rather than just the first one. This preserves all failures from concurrently-running sequences (similar to throwing from a finally block).

Solutions

  1. Unwrap errors.InnerExceptions (catch AggregateException) and inspect each faulting source; add per-source error handling (e.g. .Catch() on individual streams) before merging.
  2. Ensure each source sequence itself cannot throw (wrap source consumption with try/catch or defensive operators) so Merge has nothing to aggregate.
  3. If you only care about the first failure, note Merge intentionally aggregates; consider alternative combinators or catch AggregateException and rethrow its first InnerException.

Example fix

// before
await foreach (var item in AsyncEnumerable.Merge(streamA, streamB)) { ... }
// after
try
{
    await foreach (var item in AsyncEnumerable.Merge(
        streamA.Catch((Exception e) => AsyncEnumerable.Empty<int>()),
        streamB.Catch((Exception e) => AsyncEnumerable.Empty<int>()))) { ... }
}
catch (AggregateException ex)
{
    foreach (var inner in ex.InnerExceptions) Log(inner);
}
Defensive patterns

Strategy: try-catch

Try / catch

try { await foreach (var x in merged) ... }
catch (AggregateException agg) { foreach (var e in agg.InnerExceptions) Handle(e); }

Prevention

When it happens

Trigger: Calling AsyncEnumerable.Merge on multiple (or an array of) async sources where one or more sources throw during MoveNextAsync; the exception surfaces at the end of the merged enumeration after all sources have completed/cleaned up.

Common situations: Merging several event streams or message-queue subscriptions where one upstream is misconfigured (bad credentials, dropped connection, malformed data) and throws while others keep running; also occurs when a source's async enumerator throws during disposal cleanup.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/2ef2583847cd6304. Report an issue: GitHub.

Appendix: source

Thrown at Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Merge.cs:174

                        catch (Exception ex)
                        {
                            if (errors == null)
                            {
                                errors = new List<Exception>();
                            }

                            errors.Add(ex);
                        }
                    }

                    // NB: If we had any errors during cleaning (and awaiting pending operations), we throw these exceptions
                    //     instead of the original exception that may have led to running the finally block. This is similar
                    //     to throwing from any finally block (except that we catch all exceptions to ensure cleanup of all
                    //     concurrent sequences being merged).

                    if (errors != null)
                    {
                        throw new AggregateException(errors);
                    }
                }
            }
#else
            {
                var count = sources.Length;

                var enumerators = new IAsyncEnumerator<TSource>?[count];
                var moveNextTasks = new Task<bool>[count];

                try
                {
                    for (var i = 0; i < count; i++)
                    {
                        var enumerator = sources[i].GetAsyncEnumerator(cancellationToken);
                        enumerators[i] = enumerator;

                        // REVIEW: This follows the lead of the original implementation where we kick off MoveNextAsync

View on GitHub (pinned to 94b5d5ab91)