/// <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="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 async ValueTask <long> WriteAllAsync <T>(this ChannelWriter <T> target, IEnumerable <ValueTask <T> > source, bool complete = false, CancellationToken cancellationToken = default) { await target.WaitToWriteAndThrowIfClosedAsync( "The target channel was closed before writing could begin.", cancellationToken); long count = 0; var next = new ValueTask(); foreach (var e in source) { var value = await e.ConfigureAwait(false); await next.ConfigureAwait(false); count++; next = target.WriteAsync(value, cancellationToken); } await next.ConfigureAwait(false); if (complete) { target.Complete(); } return(count); }
/// <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="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="deferredExecution">If true, calls await Task.Yield() before writing.</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 async ValueTask <long> WriteAllAsync <T>(this ChannelWriter <T> target, IAsyncEnumerable <T> source, bool complete = false, bool deferredExecution = false, CancellationToken cancellationToken = default) { if (target is null) { throw new ArgumentNullException(nameof(target)); } if (source is null) { throw new ArgumentNullException(nameof(source)); } Contract.EndContractBlock(); await target .WaitToWriteAndThrowIfClosedAsync(ChannelClosedMessage, deferredExecution, cancellationToken) .ConfigureAwait(false); try { long count = 0; var next = new ValueTask(); await foreach (var value in source) { await next.ConfigureAwait(false); count++; next = target.WriteAsync(value, cancellationToken); } await next.ConfigureAwait(false); return(count); } catch (Exception ex) { if (complete) { target.Complete(ex); complete = false; } throw; } finally { if (complete) { target.Complete(); } } }
/// <summary> /// Consumes all lines from a TextReader and writes them to a channel. /// </summary> /// <param name="source">The text reader to consume from.</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 async ValueTask <long> WriteAllLines(this ChannelWriter <string> target, TextReader source, bool complete = false, CancellationToken cancellationToken = default) { var next = target.WaitToWriteAndThrowIfClosedAsync( "The target channel was closed before writing could begin.", cancellationToken); await next.ConfigureAwait(false); long count = 0; var more = false; // if it completed and actually returned false, no need to bubble the cancellation since it actually completed. while (!cancellationToken.IsCancellationRequested) { var line = await source.ReadLineAsync().ConfigureAwait(false); if (line == null) { more = false; break; } else { more = true; } await next.ConfigureAwait(false); count++; next = target.WriteAsync(line, cancellationToken); } await next.ConfigureAwait(false); if (more) { cancellationToken.ThrowIfCancellationRequested(); } if (complete) { target.Complete(); } return(count); }
/// <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); } }
/// <summary> /// Consumes all lines from a TextReader and writes them to a channel. /// </summary> /// <param name="source">The text reader to consume from.</param> /// <param name="target">The channel to write to.</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="deferredExecution">If true, calls await Task.Yield() before writing.</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 async ValueTask <long> WriteAllLines(this ChannelWriter <string> target, TextReader source, bool complete = false, bool deferredExecution = false, CancellationToken cancellationToken = default) { if (target is null) { throw new ArgumentNullException(nameof(target)); } if (source is null) { throw new ArgumentNullException(nameof(source)); } Contract.EndContractBlock(); var next = target.WaitToWriteAndThrowIfClosedAsync(ChannelClosedMessage, deferredExecution, cancellationToken); await next.ConfigureAwait(false); try { long count = 0; var more = false; // if it completed and actually returned false, no need to bubble the cancellation since it actually completed. while (!cancellationToken.IsCancellationRequested) { var line = await source.ReadLineAsync().ConfigureAwait(false); if (line == null) { more = false; break; } else { more = true; } await next.ConfigureAwait(false); count++; next = target.WriteAsync(line, cancellationToken); } await next.ConfigureAwait(false); if (more) { cancellationToken.ThrowIfCancellationRequested(); } return(count); } catch (Exception ex) { if (complete) { target.Complete(ex); complete = false; } throw; } finally { if (complete) { target.Complete(); } } }