Пример #1
0
        public static IAsyncEnumerable <IList <TSource> > Buffer <TSource>(this IAsyncEnumerable <TSource> source, int count, int skip)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }
            if (count <= 0)
            {
                throw Error.ArgumentOutOfRange(nameof(count));
            }
            if (skip <= 0)
            {
                throw Error.ArgumentOutOfRange(nameof(skip));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <IList <TSource> > Core(CancellationToken cancellationToken)
            {
                var buffers = new Queue <IList <TSource> >();

                var index = 0;

                await foreach (var item in AsyncEnumerableExtensions.WithCancellation(source, cancellationToken).ConfigureAwait(false))
                {
                    if (index++ % skip == 0)
                    {
                        buffers.Enqueue(new List <TSource>(count));
                    }

                    foreach (var buffer in buffers)
                    {
                        buffer.Add(item);
                    }

                    if (buffers.Count > 0 && buffers.Peek().Count == count)
                    {
                        yield return(buffers.Dequeue());
                    }
                }

                while (buffers.Count > 0)
                {
                    yield return(buffers.Dequeue());
                }
            }
#else
            return(new BufferAsyncIterator <TSource>(source, count, skip));
#endif
        }
Пример #2
0
        private static IAsyncEnumerable <TSource> CatchCore <TSource>(IEnumerable <IAsyncEnumerable <TSource> > sources)
        {
#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                var error = default(ExceptionDispatchInfo);

                foreach (var source in sources)
                {
                    await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                    {
                        error = null;

                        while (true)
                        {
                            var c = default(TSource);

                            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();
            }
#else
            return(new CatchAsyncIterator <TSource>(sources));
#endif
        }
Пример #3
0
        private static IAsyncEnumerable <TSource> DoCore <TSource>(IAsyncEnumerable <TSource> source, Func <TSource, CancellationToken, Task> onNext, Func <Exception, CancellationToken, Task> onError, Func <CancellationToken, Task> onCompleted)
        {
#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    while (true)
                    {
                        TSource item;

                        try
                        {
                            if (!await e.MoveNextAsync())
                            {
                                break;
                            }

                            item = e.Current;

                            await onNext(item, cancellationToken).ConfigureAwait(false);
                        }
                        catch (OperationCanceledException)
                        {
                            throw;
                        }
                        catch (Exception ex) when(onError != null)
                        {
                            await onError(ex, cancellationToken).ConfigureAwait(false);

                            throw;
                        }

                        yield return(item);
                    }

                    if (onCompleted != null)
                    {
                        await onCompleted(cancellationToken).ConfigureAwait(false);
                    }
                }
            }
#else
            return(new DoAsyncIteratorWithTaskAndCancellation <TSource>(source, onNext, onError, onCompleted));
#endif
        }
Пример #4
0
        /// <summary>
        /// Repeats the element indefinitely.
        /// </summary>
        /// <typeparam name="TResult">The type of the elements in the source sequence.</typeparam>
        /// <param name="element">Element to repeat.</param>
        /// <returns>The async-enumerable sequence producing the element repeatedly and sequentially.</returns>
        public static IAsyncEnumerable <TResult> Repeat <TResult>(TResult element)
        {
            return(AsyncEnumerable.Create(Core));

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
            async IAsyncEnumerator <TResult> Core(CancellationToken cancellationToken)
            {
                while (true)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    yield return(element);
                }
            }

#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        }
Пример #5
0
        /// <summary>
        /// Returns an async-enumerable sequence that starts the specified cancellable asynchronous factory function whenever a new observer subscribes.
        /// The CancellationToken passed to the asynchronous factory function is tied to the returned disposable subscription, allowing best-effort cancellation.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the sequence returned by the factory function, and in the resulting sequence.</typeparam>
        /// <param name="factory">Asynchronous factory function, supporting cancellation, to start for each consumer that starts enumerating the resulting asynchronous sequence.</param>
        /// <returns>An async-enumerable sequence whose observers trigger the given asynchronous async-enumerable factory function to be started.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="factory"/> is null.</exception>
        /// <remarks>This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11.</remarks>
        /// <remarks>When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous async-enumerable factory function will be signaled.</remarks>
        public static IAsyncEnumerable <TSource> Defer <TSource>(Func <CancellationToken, Task <IAsyncEnumerable <TSource> > > factory)
        {
            if (factory == null)
            {
                throw Error.ArgumentNull(nameof(factory));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await foreach (var item in (await factory(cancellationToken).ConfigureAwait(false)).WithCancellation(cancellationToken).ConfigureAwait(false))
                {
                    yield return(item);
                }
            }
        }
Пример #6
0
        /// <summary>
        /// Ignores all elements in an async-enumerable sequence leaving only the termination messages.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <returns>An empty async-enumerable sequence that signals termination, successful or exceptional, of the source sequence.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
        public static IAsyncEnumerable <TSource> IgnoreElements <TSource>(this IAsyncEnumerable <TSource> source)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await foreach (var _ in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                {
                }

                yield break;
            }
        }
Пример #7
0
        private static IAsyncEnumerable <TSource> DistinctUntilChangedCore <TSource, TKey>(IAsyncEnumerable <TSource> source, Func <TSource, CancellationToken, ValueTask <TKey> > keySelector, IEqualityComparer <TKey> comparer)
        {
#if USE_ASYNC_ITERATOR
            if (comparer == null)
            {
                comparer = EqualityComparer <TKey> .Default;
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    if (!await e.MoveNextAsync())
                    {
                        yield break;
                    }

                    var item = e.Current;

                    var latestKey = await keySelector(item, cancellationToken).ConfigureAwait(false);

                    yield return(item);

                    while (await e.MoveNextAsync())
                    {
                        item = e.Current;

                        var currentKey = await keySelector(item, cancellationToken).ConfigureAwait(false);

                        if (!comparer.Equals(latestKey, currentKey))
                        {
                            latestKey = currentKey;

                            yield return(item);
                        }
                    }
                }
            }
#else
            return(new DistinctUntilChangedAsyncIteratorWithTaskAndCancellation <TSource, TKey>(source, keySelector, comparer));
#endif
        }
Пример #8
0
        private static IAsyncEnumerable <TSource> DoCore <TSource>(IAsyncEnumerable <TSource> source, Func <TSource, Task> onNext, Func <Exception, Task>?onError, Func <Task>?onCompleted)
        {
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);

                while (true)
                {
                    TSource item;

                    try
                    {
                        if (!await e.MoveNextAsync())
                        {
                            break;
                        }

                        item = e.Current;

                        await onNext(item).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                        throw;
                    }
                    catch (Exception ex) when(onError != null)
                    {
                        await onError(ex).ConfigureAwait(false);

                        throw;
                    }

                    yield return(item);
                }

                if (onCompleted != null)
                {
                    await onCompleted().ConfigureAwait(false);
                }
            }
        }
Пример #9
0
        /// <summary>
        /// Repeats the async-enumerable sequence indefinitely.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
        /// <param name="source">Observable sequence to repeat.</param>
        /// <returns>The async-enumerable sequence producing the elements of the given sequence repeatedly and sequentially.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
        public static IAsyncEnumerable <TSource> Repeat <TSource>(this IAsyncEnumerable <TSource> source)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                while (true)
                {
                    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                    {
                        yield return(item);
                    }
                }
            }
        }
Пример #10
0
        public static IAsyncEnumerable <TSource> Concat <TSource>(params IAsyncEnumerable <TSource>[] sources)
        {
            if (sources == null)
            {
                throw Error.ArgumentNull(nameof(sources));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                foreach (var source in sources)
                {
                    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                    {
                        yield return(item);
                    }
                }
            }
        }
Пример #11
0
        public static IAsyncEnumerable <TSource> Defer <TSource>(Func <Task <IAsyncEnumerable <TSource> > > factory)
        {
            if (factory == null)
            {
                throw Error.ArgumentNull(nameof(factory));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await foreach (var item in (await factory().ConfigureAwait(false)).WithCancellation(cancellationToken).ConfigureAwait(false))
                {
                    yield return(item);
                }
            }
#else
            return(new AsyncDeferIterator <TSource>(factory));
#endif
        }
Пример #12
0
        private static IAsyncEnumerable <TSource> DistinctUntilChangedCore <TSource>(IAsyncEnumerable <TSource> source, IEqualityComparer <TSource> comparer)
        {
#if USE_ASYNC_ITERATOR
            if (comparer == null)
            {
                comparer = EqualityComparer <TSource> .Default;
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    if (!await e.MoveNextAsync())
                    {
                        yield break;
                    }

                    var latest = e.Current;

                    yield return(latest);

                    while (await e.MoveNextAsync())
                    {
                        var item = e.Current;

                        if (!comparer.Equals(latest, item))
                        {
                            latest = item;

                            yield return(latest);
                        }
                    }
                }
            }
#else
            return(new DistinctUntilChangedAsyncIterator <TSource>(source, comparer));
#endif
        }
Пример #13
0
        public static IAsyncEnumerable <TSource> IgnoreElements <TSource>(this IAsyncEnumerable <TSource> source)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await foreach (var _ in AsyncEnumerableExtensions.WithCancellation(source, cancellationToken).ConfigureAwait(false))
                {
                }

                yield break;
            }
#else
            return(new IgnoreElementsAsyncIterator <TSource>(source));
#endif
        }
Пример #14
0
        public static IAsyncEnumerable <IList <TSource> > Buffer <TSource>(this IAsyncEnumerable <TSource> source, int count)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }
            if (count <= 0)
            {
                throw Error.ArgumentOutOfRange(nameof(count));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <IList <TSource> > Core(CancellationToken cancellationToken)
            {
                var buffer = new List <TSource>(count);

                await foreach (var item in AsyncEnumerableExtensions.WithCancellation(source, cancellationToken).ConfigureAwait(false))
                {
                    buffer.Add(item);

                    if (buffer.Count == count)
                    {
                        yield return(buffer);

                        buffer = new List <TSource>(count);
                    }
                }

                if (buffer.Count > 0)
                {
                    yield return(buffer);
                }
            }
#else
            return(new BufferAsyncIterator <TSource>(source, count, count));
#endif
        }
Пример #15
0
        private static IAsyncEnumerable <TSource> DistinctUntilChangedCore <TSource, TKey>(IAsyncEnumerable <TSource> source, Func <TSource, CancellationToken, ValueTask <TKey> > keySelector, IEqualityComparer <TKey>?comparer)
        {
            comparer ??= EqualityComparer <TKey> .Default;

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);

                if (!await e.MoveNextAsync())
                {
                    yield break;
                }

                var item = e.Current;

                var latestKey = await keySelector(item, cancellationToken).ConfigureAwait(false);

                yield return(item);

                while (await e.MoveNextAsync())
                {
                    item = e.Current;

                    var currentKey = await keySelector(item, cancellationToken).ConfigureAwait(false);

                    // REVIEW: Need comparer!.Equals to satisfy nullable reference type warnings.

                    if (!comparer !.Equals(latestKey, currentKey))
                    {
                        latestKey = currentKey;

                        yield return(item);
                    }
                }
            }
        }
Пример #16
0
        /// <summary>
        /// Constructs an async-enumerable sequence that depends on a resource object, whose lifetime is tied to the resulting async-enumerable sequence's lifetime. The resource is obtained and used through asynchronous methods.
        /// The CancellationToken passed to the asynchronous methods is tied to the returned disposable subscription, allowing best-effort cancellation at any stage of the resource acquisition or usage.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the produced sequence.</typeparam>
        /// <typeparam name="TResource">The type of the resource used during the generation of the resulting sequence. Needs to implement <see cref="IDisposable"/>.</typeparam>
        /// <param name="resourceFactory">Asynchronous factory function to obtain a resource object.</param>
        /// <param name="enumerableFactory">Asynchronous factory function to obtain an async-enumerable sequence that depends on the obtained resource.</param>
        /// <returns>An async-enumerable sequence whose lifetime controls the lifetime of the dependent resource object.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="resourceFactory"/> or <paramref name="enumerableFactory"/> is null.</exception>
        /// <remarks>This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11.</remarks>
        /// <remarks>When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous resource factory and async-enumerable factory functions will be signaled.</remarks>
        public static IAsyncEnumerable <TSource> Using <TSource, TResource>(Func <CancellationToken, Task <TResource> > resourceFactory, Func <TResource, CancellationToken, ValueTask <IAsyncEnumerable <TSource> > > enumerableFactory) where TResource : IDisposable
        {
            if (resourceFactory == null)
            {
                throw Error.ArgumentNull(nameof(resourceFactory));
            }
            if (enumerableFactory == null)
            {
                throw Error.ArgumentNull(nameof(enumerableFactory));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                using var resource = await resourceFactory(cancellationToken).ConfigureAwait(false);

                await foreach (var item in (await enumerableFactory(resource, cancellationToken).ConfigureAwait(false)).WithCancellation(cancellationToken).ConfigureAwait(false))
                {
                    yield return(item);
                }
            }
        }
Пример #17
0
        public static IAsyncEnumerable <TSource> Scan <TSource>(this IAsyncEnumerable <TSource> source, Func <TSource, TSource, ValueTask <TSource> > accumulator)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }
            if (accumulator == null)
            {
                throw Error.ArgumentNull(nameof(accumulator));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    if (!await e.MoveNextAsync())
                    {
                        yield break;
                    }

                    var res = e.Current;

                    while (await e.MoveNextAsync())
                    {
                        res = await accumulator(res, e.Current).ConfigureAwait(false);

                        yield return(res);
                    }
                }
            }
#else
            return(new ScanAsyncEnumerableWithTask <TSource>(source, accumulator));
#endif
        }
Пример #18
0
        public static IAsyncEnumerable <TSource> Concat <TSource>(this IEnumerable <IAsyncEnumerable <TSource> > sources)
        {
            if (sources == null)
            {
                throw Error.ArgumentNull(nameof(sources));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                foreach (var source in sources)
                {
                    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                    {
                        yield return(item);
                    }
                }
            }
#else
            return(ConcatCore(sources));
#endif
        }
Пример #19
0
        /// <summary>
        /// Repeats the async-enumerable sequence a specified number of times.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
        /// <param name="source">Observable sequence to repeat.</param>
        /// <param name="count">Number of times to repeat the sequence.</param>
        /// <returns>The async-enumerable sequence producing the elements of the given sequence repeatedly.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is less than zero.</exception>
        public static IAsyncEnumerable <TSource> Repeat <TSource>(this IAsyncEnumerable <TSource> source, int count)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }
            if (count < 0)
            {
                throw Error.ArgumentOutOfRange(nameof(count));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                for (var i = 0; i < count; i++)
                {
                    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                    {
                        yield return(item);
                    }
                }
            }
        }
Пример #20
0
        public static IAsyncEnumerable <TSource> Repeat <TSource>(this IAsyncEnumerable <TSource> source)
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                while (true)
                {
                    await foreach (var item in AsyncEnumerableExtensions.WithCancellation(source, cancellationToken).ConfigureAwait(false))
                    {
                        yield return(item);
                    }
                }
            }
#else
            return(new RepeatSequenceAsyncIterator <TSource>(source, -1));
#endif
        }
Пример #21
0
        /// <summary>
        /// Invokes a specified action after the source async-enumerable sequence terminates gracefully or exceptionally.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <param name="finallyAction">Action to invoke after the source async-enumerable sequence terminates.</param>
        /// <returns>Source sequence with the action-invoking termination behavior applied.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="finallyAction"/> is null.</exception>
        public static IAsyncEnumerable<TSource> Finally<TSource>(this IAsyncEnumerable<TSource> source, Action finallyAction)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (finallyAction == null)
                throw Error.ArgumentNull(nameof(finallyAction));

            return AsyncEnumerable.Create(Core);

            async IAsyncEnumerator<TSource> Core(CancellationToken cancellationToken)
            {
                try
                {
                    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                    {
                        yield return item;
                    }
                }
                finally
                {
                    finallyAction();
                }
            }
        }
Пример #22
0
        public static IAsyncEnumerable <TSource> Amb <TSource>(params IAsyncEnumerable <TSource>[] sources)
        {
            if (sources == null)
            {
                throw Error.ArgumentNull(nameof(sources));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                //
                // REVIEW: See remarks on binary overload for changes compared to the original.
                //

                var n = sources.Length;

                var enumerators            = new IAsyncEnumerator <TSource> [n];
                var moveNexts              = new Task <bool> [n];
                var individualTokenSources = new CancellationTokenSource[n];

                for (var i = 0; i < n; i++)
                {
                    individualTokenSources[i] = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                }

                try
                {
                    for (var i = 0; i < n; i++)
                    {
                        var enumerator = sources[i].GetAsyncEnumerator(individualTokenSources[i].Token);

                        enumerators[i] = enumerator;
                        moveNexts[i]   = enumerator.MoveNextAsync().AsTask();
                    }
                }
                catch
                {
                    var cleanup = new Task[n];

                    for (var i = n - 1; i >= 0; i--)
                    {
                        individualTokenSources[i].Cancel();

                        cleanup[i] = AwaitMoveNextAsyncAndDispose(moveNexts[i], enumerators[i]);
                    }

                    await Task.WhenAll(cleanup).ConfigureAwait(false);

                    throw;
                }

                var moveNextWinner = await Task.WhenAny(moveNexts).ConfigureAwait(false);

                //
                // NB: The use of IndexOf is fine. If task N completed by returning a ValueTask<bool>
                //     which is equivalent to the task returned by task M (where M < N), AsTask may
                //     return the same reference (e.g. due to caching of completed Boolean tasks). In
                //     such a case, IndexOf will find task M rather than N in the array, but both have
                //     an equivalent completion state (because they're reference equal). This only leads
                //     to a left-bias in selection of sources, but given Amb's "ambiguous" nature, this
                //     is acceptable.
                //

                var winnerIndex = Array.IndexOf(moveNexts, moveNextWinner);

                var winner = enumerators[winnerIndex];

                var loserCleanupTasks = new List <Task>(n - 1);

                for (var i = n - 1; i >= 0; i--)
                {
                    if (i != winnerIndex)
                    {
                        individualTokenSources[i].Cancel();
                        var loserCleanupTask = AwaitMoveNextAsyncAndDispose(moveNexts[i], enumerators[i]);
                        loserCleanupTasks.Add(loserCleanupTask);
                    }
                }

                var cleanupLosers = Task.WhenAll(loserCleanupTasks);

                try
                {
                    await using (winner.ConfigureAwait(false))
                    {
                        if (!await moveNextWinner.ConfigureAwait(false))
                        {
                            yield break;
                        }

                        yield return(winner.Current);

                        while (await winner.MoveNextAsync().ConfigureAwait(false))
                        {
                            yield return(winner.Current);
                        }
                    }
                }
                finally
                {
                    await cleanupLosers.ConfigureAwait(false);
                }
            }
        }
Пример #23
0
        public static IAsyncEnumerable <TSource> Merge <TSource>(params IAsyncEnumerable <TSource>[] sources)
        {
            if (sources == null)
            {
                throw Error.ArgumentNull(nameof(sources));
            }

#if USE_FAIR_AND_CHEAPER_MERGE
            //
            // This new implementation of Merge differs from the original one in a few ways:
            //
            // - It's cheaper because:
            //   - no conversion from ValueTask<bool> to Task<bool> takes place using AsTask,
            //   - we don't instantiate Task.WhenAny tasks for each iteration.
            // - It's fairer because:
            //   - the MoveNextAsync tasks are awaited concurently, but completions are queued,
            //     instead of awaiting a new WhenAny task where "left" sources have preferential
            //     treatment over "right" sources.
            //

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                var count = sources.Length;

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

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

                        // REVIEW: This follows the lead of the original implementation where we kick off MoveNextAsync
                        //         operations immediately. An alternative would be to do this in a separate stage, thus
                        //         preventing concurrency across MoveNextAsync and GetAsyncEnumerator calls and avoiding
                        //         any MoveNextAsync calls before all enumerators are acquired (or an exception has
                        //         occurred doing so).

                        moveNextTasks[i] = enumerator.MoveNextAsync();
                    }

                    var whenAny = TaskExt.WhenAny(moveNextTasks);

                    int active = count;

                    while (active > 0)
                    {
                        int index = await whenAny;

                        IAsyncEnumerator <TSource> enumerator   = enumerators[index];
                        ValueTask <bool>           moveNextTask = moveNextTasks[index];

                        if (!await moveNextTask.ConfigureAwait(false))
                        {
                            //
                            // Replace the task in our array by a completed task to make finally logic easier. Note that
                            // the WhenAnyValueTask object has a reference to our array (i.e. no copy is made), so this
                            // gets rid of any resources the original task may have held onto. However, we *don't* call
                            // whenAny.Replace to set this value, because it'd attach an awaiter to the already completed
                            // task, causing spurious wake-ups when awaiting whenAny.
                            //

                            moveNextTasks[index] = new ValueTask <bool>();

                            // REVIEW: The original implementation did not dispose eagerly, which could lead to resource
                            //         leaks when merged with other long-running sequences.

                            enumerators[index] = null; // NB: Avoids attempt at double dispose in finally if disposing fails.
                            await enumerator.DisposeAsync().ConfigureAwait(false);

                            active--;
                        }
                        else
                        {
                            TSource item = enumerator.Current;

                            //
                            // Replace the task using whenAny.Replace, which will write it to the moveNextTasks array, and
                            // will start awaiting the task. Note we don't have to write to moveNextTasks ourselves because
                            // the whenAny object has a reference to it (i.e. no copy is made).
                            //

                            whenAny.Replace(index, enumerator.MoveNextAsync());

                            yield return(item);
                        }
                    }
                }
                finally
                {
                    // REVIEW: The original implementation performs a concurrent dispose, which seems undesirable given the
                    //         additional uncontrollable source of concurrency and the sequential resource acquisition. In
                    //         this modern implementation, we release resources in opposite order as we acquired them, thus
                    //         guaranteeing determinism (and mimicking a series of nested `await using` statements).

                    // REVIEW: If we decide to phase GetAsyncEnumerator and the initial MoveNextAsync calls at the start of
                    //         the operator implementation, we should make this symmetric and first await all in flight
                    //         MoveNextAsync operations, prior to disposing the enumerators.

                    var errors = default(List <Exception>);

                    for (var i = count - 1; i >= 0; i--)
                    {
                        ValueTask <bool>           moveNextTask = moveNextTasks[i];
                        IAsyncEnumerator <TSource> enumerator   = enumerators[i];

                        try
                        {
                            try
                            {
                                //
                                // Await the task to ensure outstanding work is completed prior to performing a dispose
                                // operation. Note that we don't have to do anything special for tasks belonging to
                                // enumerators that have finished; we swapped in a placeholder completed task.
                                //

                                // REVIEW: This adds an additional continuation to all of the pending tasks (note that
                                //         whenAny also has registered one). The whenAny object will be collectible
                                //         after all of these complete. Alternatively, we could drain via whenAny, by
                                //         awaiting it until the active count drops to 0. This saves on attaching the
                                //         additional continuations, but we need to decide on order of dispose. Right
                                //         now, we dispose in opposite order of acquiring the enumerators, with the
                                //         exception of enumerators that were disposed eagerly upon early completion.
                                //         Should we care about the dispose order at all?

                                _ = await moveNextTask.ConfigureAwait(false);
                            }
                            finally
                            {
                                if (enumerator != null)
                                {
                                    await enumerator.DisposeAsync().ConfigureAwait(false);
                                }
                            }
                        }
                        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);
                    }
                }
            }
#elif USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                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
                        //         operations immediately. An alternative would be to do this in a separate stage, thus
                        //         preventing concurrency across MoveNextAsync and GetAsyncEnumerator calls and avoiding
                        //         any MoveNextAsync calls before all enumerators are acquired (or an exception has
                        //         occurred doing so).

                        moveNextTasks[i] = enumerator.MoveNextAsync().AsTask();
                    }

                    var active = count;

                    while (active > 0)
                    {
                        // REVIEW: Performance of WhenAny may be an issue when called repeatedly like this. We should
                        //         measure and could consider operating directly on the ValueTask<bool> objects, thus
                        //         also preventing the Task<bool> allocations from AsTask.

                        var moveNextTask = await Task.WhenAny(moveNextTasks).ConfigureAwait(false);

                        // REVIEW: This seems wrong. AsTask can return the original Task<bool> (if the ValueTask<bool>
                        //         is wrapping one) or return a singleton instance for true and false, at which point
                        //         the use of IndexOf may pick an element closer to the start of the array because of
                        //         reference equality checks and aliasing effects. See GetTaskForResult in the BCL.

                        var index = Array.IndexOf(moveNextTasks, moveNextTask);

                        var enumerator = enumerators[index];

                        if (!await moveNextTask.ConfigureAwait(false))
                        {
                            moveNextTasks[index] = TaskExt.Never;

                            // REVIEW: The original implementation did not dispose eagerly, which could lead to resource
                            //         leaks when merged with other long-running sequences.

                            enumerators[index] = null; // NB: Avoids attempt at double dispose in finally if disposing fails.
                            await enumerator.DisposeAsync().ConfigureAwait(false);

                            active--;
                        }
                        else
                        {
                            var item = enumerator.Current;

                            moveNextTasks[index] = enumerator.MoveNextAsync().AsTask();

                            yield return(item);
                        }
                    }
                }
                finally
                {
                    // REVIEW: The original implementation performs a concurrent dispose, which seems undesirable given the
                    //         additional uncontrollable source of concurrency and the sequential resource acquisition. In
                    //         this modern implementation, we release resources in opposite order as we acquired them, thus
                    //         guaranteeing determinism (and mimicking a series of nested `await using` statements).

                    // REVIEW: If we decide to phase GetAsyncEnumerator and the initial MoveNextAsync calls at the start of
                    //         the operator implementation, we should make this symmetric and first await all in flight
                    //         MoveNextAsync operations, prior to disposing the enumerators.

                    var errors = default(List <Exception>);

                    for (var i = count - 1; i >= 0; i--)
                    {
                        var moveNextTask = moveNextTasks[i];
                        var enumerator   = enumerators[i];

                        try
                        {
                            try
                            {
                                if (moveNextTask != null && moveNextTask != TaskExt.Never)
                                {
                                    _ = await moveNextTask.ConfigureAwait(false);
                                }
                            }
                            finally
                            {
                                if (enumerator != null)
                                {
                                    await enumerator.DisposeAsync().ConfigureAwait(false);
                                }
                            }
                        }
                        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
            return(new MergeAsyncIterator <TSource>(sources));
#endif
        }
Пример #24
0
        public static IAsyncEnumerable <TSource> Amb <TSource>(this IAsyncEnumerable <TSource> first, IAsyncEnumerable <TSource> second)
        {
            if (first == null)
            {
                throw Error.ArgumentNull(nameof(first));
            }
            if (second == null)
            {
                throw Error.ArgumentNull(nameof(second));
            }

            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                IAsyncEnumerator <TSource>?firstEnumerator  = null;
                IAsyncEnumerator <TSource>?secondEnumerator = null;

                Task <bool>?firstMoveNext  = null;
                Task <bool>?secondMoveNext = null;

                //
                // We need separate tokens for each source so that the non-winner can get disposed and unblocked
                // i.e., see Never()
                //

                var firstCancelToken  = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                var secondCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

                try
                {
                    //
                    // REVIEW: We start both sequences unconditionally. An alternative implementation could be to just stick
                    //         to the first sequence if we notice it already has a value (or exception) available. This would
                    //         be similar to Task.WhenAny behavior (see CommonCWAnyLogic in dotnet/coreclr). We could consider
                    //         adding a WhenAny combinator that does exactly that. We can even avoid calling AsTask.
                    //

                    firstEnumerator = first.GetAsyncEnumerator(firstCancelToken.Token);
                    firstMoveNext   = firstEnumerator.MoveNextAsync().AsTask();

                    //
                    // REVIEW: Order of operations has changed here compared to the original, but is now in sync with the N-ary
                    //         overload which performs GetAsyncEnumerator/MoveNextAsync in pairs, rather than phased.
                    //

                    secondEnumerator = second.GetAsyncEnumerator(secondCancelToken.Token);
                    secondMoveNext   = secondEnumerator.MoveNextAsync().AsTask();
                }
                catch
                {
                    secondCancelToken.Cancel();
                    firstCancelToken.Cancel();

                    // NB: AwaitMoveNextAsyncAndDispose checks for null for both arguments, reducing the need for many null
                    //     checks over here.

                    var cleanup = new[]
                    {
                        AwaitMoveNextAsyncAndDispose(secondMoveNext, secondEnumerator),
                        AwaitMoveNextAsyncAndDispose(firstMoveNext, firstEnumerator)
                    };


                    await Task.WhenAll(cleanup).ConfigureAwait(false);

                    throw;
                }

                //
                // REVIEW: Consider using the WhenAny combinator defined for Merge in TaskExt, which would avoid the need
                //         to convert to Task<bool> prior to calling Task.WhenAny.
                //

                var moveNextWinner = await Task.WhenAny(firstMoveNext, secondMoveNext).ConfigureAwait(false);

                //
                // REVIEW: An alternative option is to call DisposeAsync on the other and await it, but this has two drawbacks:
                //
                // 1. Concurrent DisposeAsync while a MoveNextAsync is in flight.
                // 2. The winner elected by Amb is blocked to yield results until the loser unblocks.
                //

                IAsyncEnumerator <TSource> winner;
                Task disposeLoser;

                if (moveNextWinner == firstMoveNext)
                {
                    winner = firstEnumerator;
                    secondCancelToken.Cancel();
                    disposeLoser = AwaitMoveNextAsyncAndDispose(secondMoveNext, secondEnumerator);
                }
                else
                {
                    winner = secondEnumerator;
                    firstCancelToken.Cancel();
                    disposeLoser = AwaitMoveNextAsyncAndDispose(firstMoveNext, firstEnumerator);
                }

                try
                {
                    await using (winner.ConfigureAwait(false))
                    {
                        if (!await moveNextWinner.ConfigureAwait(false))
                        {
                            yield break;
                        }

                        yield return(winner.Current);

                        while (await winner.MoveNextAsync().ConfigureAwait(false))
                        {
                            yield return(winner.Current);
                        }
                    }
                }
                finally
                {
                    //
                    // REVIEW: This behavior differs from the original implementation in that we never discard any in flight
                    //         asynchronous operations. If an exception occurs while enumerating the winner, it can be
                    //         subsumed by an exception thrown due to cleanup of the loser. Also, if Amb is used to deal with
                    //         a potentially long-blocking sequence, this implementation would transfer this blocking behavior
                    //         to the resulting sequence. However, it seems never discarding a non-completed task should be a
                    //         general design tenet, and fire-and-forget dispose behavior could be added as another "unsafe"
                    //         operator, so all such sins are made explicit by the user. Nonetheless, this change is a breaking
                    //         change for Ix Async.
                    //

                    await disposeLoser.ConfigureAwait(false);
                }
            }
        }
Пример #25
0
        public static IAsyncEnumerable <TSource> Catch <TSource, TException>(this IAsyncEnumerable <TSource> source, Func <TException, ValueTask <IAsyncEnumerable <TSource> > > handler)
            where TException : Exception
        {
            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }
            if (handler == null)
            {
                throw Error.ArgumentNull(nameof(handler));
            }

#if USE_ASYNC_ITERATOR
            return(AsyncEnumerable.Create(Core));

            async IAsyncEnumerator <TSource> Core(CancellationToken cancellationToken)
            {
                // 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 <TSource>);

                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    while (true)
                    {
                        var c = default(TSource);

                        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 AsyncEnumerableExtensions.WithCancellation(err, cancellationToken).ConfigureAwait(false))
                    {
                        yield return(item);
                    }
                }
            }
#else
            return(new CatchAsyncIteratorWithTask <TSource, TException>(source, handler));
#endif
        }