示例#1
0
        /// <summary>
        /// Completes a consumer with an exception.
        /// </summary>
        internal static void Fail <T>(this AsyncEnumerator <T> .AsyncConsumer consumer, Exception exception)
        {
            var src = new TaskCompletionSource <int>();

            src.SetException(exception);
            consumer.OnComplete(src.Task);
        }
示例#2
0
        /// <summary>
        /// Completes a consumer.
        /// </summary>
        internal static void Complete <T>(this AsyncEnumerator <T> .AsyncConsumer consumer)
        {
            var src = new TaskCompletionSource <int>();

            src.SetResult(1);
            consumer.OnComplete(src.Task);
        }
示例#3
0
 internal static IAsyncEnumerable <T> Empty <T>()
 {
     return(new DelegatingAsyncEnumerable <T>(() =>
     {
         var enumerator = new AsyncEnumerator <T>();
         enumerator.Consumer.Complete();
         return enumerator;
     }));
 }
示例#4
0
        private static async void PopulateAsync <T>(this AsyncEnumerator <T> enumerator, Func <IAsyncConsumer <T>, Task> producer)
        {
            var consumer = enumerator.Consumer;
            await consumer.WaitForStartAsync();

            // Instead of awaiting the producer task, we use an explicit continuation here
            // because we want to propagate any outcome, especially exceptions, to the consumer.
            producer(consumer).ContinueWith(consumer.OnComplete);
        }
示例#5
0
        /// <summary>
        /// Creates an <see cref="IAsyncEnumerable{T}"/> instance from this iterator function.
        /// </summary>
        /// <typeparam name="T">The type of the elements in the returned enumerable.</typeparam>
        /// <param name="producer">The asynchronous function used to populate the returned enumerable.</param>
        /// <returns>An instance of <c>IAsyncEnumerable</c> that represents the items yielded by the function provided in <c>producer</c>.</returns>
        public static IAsyncEnumerable <T> Enumerate <T>(Func <IAsyncConsumer <T>, Task> producer)
        {
            if (producer == null)
            {
                throw new ArgumentNullException("producer");
            }

            return(new DelegatingAsyncEnumerable <T>(() =>
            {
                var enumerator = new AsyncEnumerator <T>();
                enumerator.PopulateAsync(producer);
                return enumerator;
            }));
        }
示例#6
0
        /// <summary>
        /// Groups the elements of a sequence according to a specified key selector function.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of <c>source</c>.</typeparam>
        /// <typeparam name="TKey">The type of the key returned by <c>keySelector</c>.</typeparam>
        /// <typeparam name="TElement"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source">An IEnumerable{T} whose elements to group.</param>
        /// <param name="keySelector">A function to extract the key for each element.</param>
        /// <param name="elementSelector">A function to create an intermediate item for each element.</param>
        /// <param name="resultSelector">A function that creates an element in the returned sequence from a key and an intermediate item.</param>
        /// <param name="comparer">An IEqualityComparer{T} to compare keys with.</param>
        /// <returns>An IAsyncEnumerable{TResult} where each item represents a group of source items and an according key.</returns>
        public static IAsyncEnumerable <TResult> GroupBy <TSource, TKey, TElement, TResult>(
            this IAsyncEnumerable <TSource> source,
            Func <TSource, TKey> keySelector,
            Func <TSource, TElement> elementSelector,
            Func <TKey, IAsyncEnumerable <TElement>, TResult> resultSelector,
            IEqualityComparer <TKey> comparer
            )
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            if (elementSelector == null)
            {
                throw new ArgumentNullException("elementSelector");
            }

            if (resultSelector == null)
            {
                throw new ArgumentNullException("resultSelector");
            }

            return(Enumerate <TResult>(async consumer =>
            {
                // consumers of inner grouping enumerators by key
                var innerConsumersByKeys = new Dictionary <TKey, AsyncEnumerator <TElement> .AsyncConsumer>(comparer);

                // The following dictionaries hold tasks for awaiting demand in inner enumerators and according keys
                // keys by task
                var keysByWaitingForInnerMoveNextTask = new Dictionary <Task, TKey>();
                // tasks by key
                var waitingForInnerMoveNextTasksByKey = new Dictionary <TKey, Task>(comparer);

                // Waiter used to await demand for either the outer enumerator or inner grouping enumerators
                var waiter = new MultiWaiter <Task>();

                // Keys that currently exists demanded for. These keys are not currently awaited by waiter
                var pendingKeys = new HashSet <TKey>(comparer);

                // Task for awaiting demand for the next grouping (outer enumerator)
                Task waitingOuterMoveNextTask = null;

                try
                {
                    var sourceEnumerator = source.GetEnumerator();
                    bool completed = false;
                    do
                    {
                        do
                        {
                            completed = !await sourceEnumerator.MoveNextAsync();
                            if (completed)
                            {
                                break;
                            }

                            var sourceItem = sourceEnumerator.Current;
                            var intermediateItem = elementSelector(sourceItem);
                            var key = keySelector(sourceItem);

                            AsyncEnumerator <TElement> .AsyncConsumer innerConsumer;
                            if (!innerConsumersByKeys.TryGetValue(key, out innerConsumer))
                            {
                                // So we have a new key and therefore a new grouping
                                var groupEnumerator = new AsyncEnumerator <TElement>();
                                innerConsumer = groupEnumerator.Consumer;
                                innerConsumersByKeys.Add(key, innerConsumer);

                                if (waitingOuterMoveNextTask != null)
                                {
                                    // If we are currently waiting for demand of the outer enumerator, we remove the task from the waiter
                                    // as we will get a new waiter shortly.
                                    waiter.Remove(waitingOuterMoveNextTask);
                                }
                                waitingOuterMoveNextTask = consumer.YieldAsync(resultSelector(key, groupEnumerator.AsCachedEnumerable()));
                                if (waitingOuterMoveNextTask.IsCompleted)
                                {
                                    waitingOuterMoveNextTask = null;
                                }
                                else
                                {
                                    waiter.Add(waitingOuterMoveNextTask);
                                    // It's a new key, so we definitely have demand for it. However, we will satisfy
                                    // the demand inside this iteration.
                                    pendingKeys.Add(key);
                                }
                            }
                            else
                            {
                                // So we created the grouping already.
                                Task oldTask;
                                if (waitingForInnerMoveNextTasksByKey.TryGetValue(key, out oldTask))
                                {
                                    // remove this group from pending waiters because we will get a new waiter task shortly
                                    waitingForInnerMoveNextTasksByKey.Remove(key);
                                    keysByWaitingForInnerMoveNextTask.Remove(oldTask);
                                    waiter.Remove(oldTask);
                                }
                                else
                                {
                                    // The only reason why we wouldn't be waiting for demand is, that
                                    // the group is already pending.
                                    Debug.Assert(pendingKeys.Contains(key));
                                }
                            }

                            var newTask = innerConsumer.YieldAsync(intermediateItem);

                            if (newTask.IsCompleted)
                            {
                                // If the task was completed synchronously, we just need to add the key
                                // to the set of pending keys but don't need to remember the completed task.
                                pendingKeys.Add(key);
                            }
                            else
                            {
                                waitingForInnerMoveNextTasksByKey.Add(key, newTask);
                                keysByWaitingForInnerMoveNextTask.Add(newTask, key);
                                waiter.Add(newTask);
                                pendingKeys.Remove(key);
                            }
                        } while ((pendingKeys.Count > 0) || (waitingOuterMoveNextTask == null));

                        if (completed)
                        {
                            break;
                        }

                        // We use a MultiWaiter to await demand instead of using TaskEx.WhenAny because
                        // of performance issues ( O(N log(N)) instead of O(N^2) )
                        Task nextTask = await waiter.WaitNextAsync();

                        if (nextTask == waitingOuterMoveNextTask)
                        {
                            waitingOuterMoveNextTask = null;
                        }
                        else
                        {
                            pendingKeys.Add(keysByWaitingForInnerMoveNextTask[nextTask]);
                        }
                    } while (!completed);
                }
                catch (Exception e)
                {
                    foreach (var innerConsumer in innerConsumersByKeys.Values)
                    {
                        innerConsumer.Fail(e);
                    }

                    throw;
                }
                finally
                {
                    waiter.Dispose();
                }

                foreach (var innerConsumer in innerConsumersByKeys.Values)
                {
                    innerConsumer.Complete();
                }
            }));
        }
示例#7
0
 public AsyncConsumer(AsyncEnumerator <T> parent)
 {
     this.parent = parent;
 }