// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; namespace System.Linq { public static partial class AsyncEnumerableEx { // REVIEW: All Catch operators may catch OperationCanceledException due to cancellation of the enumeration // of the source. Should we explicitly avoid handling this? E.g. as follows: // // catch (TException ex) when(!(ex is OperationCanceledException oce && oce.CancellationToken == cancellationToken)) /// /// Continues an async-enumerable sequence that is terminated by an exception of the specified type with the async-enumerable sequence produced by the handler. /// /// The type of the elements in the source sequence and sequences returned by the exception handler function. /// The type of the exception to catch and handle. Needs to derive from . /// Source sequence. /// Exception handler function, producing another async-enumerable sequence. /// An async-enumerable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting async-enumerable sequence in case an exception occurred. /// or is null. public static IAsyncEnumerable Catch(this IAsyncEnumerable source, Func> handler) where TException : Exception { if (source == null) throw Error.ArgumentNull(nameof(source)); if (handler == null) throw Error.ArgumentNull(nameof(handler)); return Core(source, handler); static async IAsyncEnumerable Core(IAsyncEnumerable source, Func> handler, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { // REVIEW: This implementation mirrors the Ix implementation, which does not protect GetEnumerator // using the try statement either. A more trivial implementation would use await foreach // and protect the entire loop using a try statement, with two breaking changes: // // - Also protecting the call to GetAsyncEnumerator by the try statement. // - Invocation of the handler after disposal of the failed first sequence. var err = default(IAsyncEnumerable); await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false)) { while (true) { TSource c; try { if (!await e.MoveNextAsync()) break; c = e.Current; } catch (TException ex) { err = handler(ex); break; } yield return c; } } if (err != null) { await foreach (var item in err.WithCancellation(cancellationToken).ConfigureAwait(false)) { yield return item; } } } } /// /// Continues an async-enumerable sequence that is terminated by an exception of the specified type with the async-enumerable sequence produced asynchronously by the handler. /// /// The type of the elements in the source sequence and sequences returned by the exception handler function. /// The type of the exception to catch and handle. Needs to derive from . /// Source sequence. /// Exception handler function, producing another async-enumerable sequence asynchronously. /// An async-enumerable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting async-enumerable sequence in case an exception occurred. /// or is null. public static IAsyncEnumerable Catch(this IAsyncEnumerable source, Func>> handler) where TException : Exception { if (source == null) throw Error.ArgumentNull(nameof(source)); if (handler == null) throw Error.ArgumentNull(nameof(handler)); return Core(source, handler); static async IAsyncEnumerable Core(IAsyncEnumerable source, Func>> handler, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { // REVIEW: This implementation mirrors the Ix implementation, which does not protect GetEnumerator // using the try statement either. A more trivial implementation would use await foreach // and protect the entire loop using a try statement, with two breaking changes: // // - Also protecting the call to GetAsyncEnumerator by the try statement. // - Invocation of the handler after disposal of the failed first sequence. var err = default(IAsyncEnumerable); await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false)) { while (true) { TSource c; try { if (!await e.MoveNextAsync()) break; c = e.Current; } catch (TException ex) { err = await handler(ex).ConfigureAwait(false); break; } yield return c; } } if (err != null) { await foreach (var item in err.WithCancellation(cancellationToken).ConfigureAwait(false)) { yield return item; } } } } #if !NO_DEEP_CANCELLATION /// /// Continues an async-enumerable sequence that is terminated by an exception of the specified type with the async-enumerable sequence produced asynchronously (cancellable) by the handler. /// /// The type of the elements in the source sequence and sequences returned by the exception handler function. /// The type of the exception to catch and handle. Needs to derive from . /// Source sequence. /// Exception handler function, producing another async-enumerable sequence asynchronously while supporting cancellation. /// An async-enumerable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting async-enumerable sequence in case an exception occurred. /// or is null. public static IAsyncEnumerable Catch(this IAsyncEnumerable source, Func>> handler) where TException : Exception { if (source == null) throw Error.ArgumentNull(nameof(source)); if (handler == null) throw Error.ArgumentNull(nameof(handler)); return Core(source, handler); static async IAsyncEnumerable Core(IAsyncEnumerable source, Func>> handler, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { // REVIEW: This implementation mirrors the Ix implementation, which does not protect GetEnumerator // using the try statement either. A more trivial implementation would use await foreach // and protect the entire loop using a try statement, with two breaking changes: // // - Also protecting the call to GetAsyncEnumerator by the try statement. // - Invocation of the handler after disposal of the failed first sequence. var err = default(IAsyncEnumerable); await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false)) { while (true) { TSource c; try { if (!await e.MoveNextAsync()) break; c = e.Current; } catch (TException ex) { err = await handler(ex, cancellationToken).ConfigureAwait(false); break; } yield return c; } } if (err != null) { await foreach (var item in err.WithCancellation(cancellationToken).ConfigureAwait(false)) { yield return item; } } } } #endif /// /// Continues an async-enumerable sequence that is terminated by an exception with the next async-enumerable sequence. /// /// The type of the elements in the source and handler sequences. /// Observable sequences to catch exceptions for. /// An async-enumerable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. /// is null. public static IAsyncEnumerable Catch(this IEnumerable> sources) { if (sources == null) throw Error.ArgumentNull(nameof(sources)); return CatchCore(sources); } /// /// Continues an async-enumerable sequence that is terminated by an exception with the next async-enumerable sequence. /// /// The type of the elements in the source and handler sequences. /// Observable sequences to catch exceptions for. /// An async-enumerable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. /// is null. public static IAsyncEnumerable Catch(params IAsyncEnumerable[] sources) { if (sources == null) throw Error.ArgumentNull(nameof(sources)); return CatchCore(sources); } /// /// Continues an async-enumerable sequence that is terminated by an exception with the next async-enumerable sequence. /// /// The type of the elements in the source sequence and handler sequence. /// First async-enumerable sequence whose exception (if any) is caught. /// Second async-enumerable sequence used to produce results when an error occurred in the first sequence. /// An async-enumerable sequence containing the first sequence's elements, followed by the elements of the second sequence in case an exception occurred. /// or is null. public static IAsyncEnumerable Catch(this IAsyncEnumerable first, IAsyncEnumerable second) { if (first == null) throw Error.ArgumentNull(nameof(first)); if (second == null) throw Error.ArgumentNull(nameof(second)); return CatchCore(new[] { first, second }); } private static async IAsyncEnumerable CatchCore(IEnumerable> sources, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { var error = default(ExceptionDispatchInfo); foreach (var source in sources) { await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false); error = null; while (true) { TSource c; try { if (!await e.MoveNextAsync()) break; c = e.Current; } catch (Exception ex) { error = ExceptionDispatchInfo.Capture(ex); break; } yield return c; } if (error == null) break; } error?.Throw(); } } }