/// <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); } }