Exemple #1
0
        /// <summary>
        /// Asynchronously writes all entries from the source to the channel.
        /// </summary>
        /// <typeparam name="T">The input type of the channel.</typeparam>
        /// <param name="target">The channel to write to.</param>
        /// <param name="maxConcurrency">The maximum number of concurrent operations.</param>
        /// <param name="source">The asynchronous source data to use.</param>
        /// <param name="complete">If true, will call .Complete() if all the results have successfully been written (or the source is emtpy).</param>
        /// <param name="cancellationToken">An optional cancellation token.</param>
        /// <returns>A task containing the count of items written that completes when all the data has been written to the channel writer.
        /// The count should be ignored if the number of iterations could exceed the max value of long.</returns>
        public static Task <long> WriteAllConcurrentlyAsync <T>(this ChannelWriter <T> target,
                                                                int maxConcurrency, IEnumerable <ValueTask <T> > source, bool complete = false, CancellationToken cancellationToken = default)
        {
            if (maxConcurrency < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(maxConcurrency), maxConcurrency, "Must be at least 1.");
            }
            Contract.EndContractBlock();

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <long>(cancellationToken));
            }

            if (maxConcurrency == 1)
            {
                return(target.WriteAllAsync(source, complete, cancellationToken).AsTask());
            }

            var shouldWait = target
                             .WaitToWriteAndThrowIfClosedAsync("The target channel was closed before writing could begin.", cancellationToken)
                             .AsTask();    // ValueTasks can only have a single await.

            var enumerator = source.GetEnumerator();
            var writers    = new Task <long> [maxConcurrency];

            for (var w = 0; w < maxConcurrency; w++)
            {
                writers[w] = WriteAllAsyncCore();
            }

            return(Task
                   .WhenAll(writers)
                   .ContinueWith(t =>
            {
                if (complete)
                {
                    target.Complete();
                }

                if (t.IsFaulted)
                {
                    return Task.FromException <long>(t.Exception);
                }

                if (t.IsCanceled)
                {
                    return Task.FromCanceled <long>(cancellationToken);
                }

                return Task.FromResult(t.Result.Sum());
            }, TaskContinuationOptions.ExecuteSynchronously)
                   .Unwrap());

            // returns false if there's no more (wasn't cancelled).
            async Task <long> WriteAllAsyncCore()
            {
                await shouldWait;
                long  count = 0;
                var   next  = new ValueTask();
                var   potentiallyCancelled = true;               // if it completed and actually returned false, no need to bubble the cancellation since it actually completed.

                while (!cancellationToken.IsCancellationRequested &&
                       (potentiallyCancelled = TryMoveNextSynchronized(enumerator, out var e)))
                {
                    var value = await e.ConfigureAwait(false);

                    await next.ConfigureAwait(false);

                    count++;
                    next = target.TryWrite(value)                     // do this to avoid unneccesary early cancel.
                                                ? new ValueTask()
                                                : target.WriteAsync(value, cancellationToken);
                }
                await next.ConfigureAwait(false);

                if (potentiallyCancelled)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
                return(count);
            }
        }