public static async Task ForEachAsync <T>(IEnumerable <T> container, Action <T, CancellationToken> action, CancellationToken token, int threadCount = -1) { ThrowIfInvalidForEachArgument(container, action); await ParallelForEachContext <T> .ForEachAsync(container, action, token, threadCount); }
/// <summary> /// Invokes an asynchronous action on each item in the collection in parallel /// </summary> /// <typeparam name="T">The type of an item</typeparam> /// <param name="enumerator">The collection of items to perform actions on</param> /// <param name="asyncItemAction">An asynchronous action to perform on the item, where first argument is the item and second argument is item's index in the collection</param> /// <param name="maxDegreeOfParallelism">Maximum items to schedule processing in parallel. The actual concurrency level depends on TPL settings. Set to 0 to choose a default value based on processor count.</param> /// <param name="breakLoopOnException">Set to True to stop processing items when first exception occurs. The result <see cref="AggregateException"/> might contain several exceptions though when faulty tasks finish at the same time.</param> /// <param name="gracefulBreak">If True (the default behavior), waits on completion for all started tasks when the loop breaks due to cancellation or an exception</param> /// <param name="cancellationToken">Cancellation token</param> /// <exception cref="ParallelForEachException">Wraps any exception(s) that occurred inside <paramref name="asyncItemAction"/></exception> /// <exception cref="OperationCanceledException">Thrown when the loop is canceled with <paramref name="cancellationToken"/></exception> public static Task ParallelForEachAsync <T>( this IAsyncEnumerator <T> enumerator, Func <T, long, Task> asyncItemAction, int maxDegreeOfParallelism, bool breakLoopOnException, bool gracefulBreak, CancellationToken cancellationToken = default) { if (enumerator == null) { throw new ArgumentNullException(nameof(enumerator)); } if (asyncItemAction == null) { throw new ArgumentNullException(nameof(asyncItemAction)); } var context = new ParallelForEachContext(maxDegreeOfParallelism, breakLoopOnException, gracefulBreak, cancellationToken); Task.Run( async() => { try { var itemIndex = 0L; while (await enumerator.MoveNextAsync().ConfigureAwait(false)) { if (context.IsLoopBreakRequested) { break; } await context.OnStartOperationAsync(cancellationToken).ConfigureAwait(false); if (context.IsLoopBreakRequested) { context.OnOperationComplete(); break; } Task itemActionTask = null; try { itemActionTask = asyncItemAction(enumerator.Current, itemIndex); } // there is no guarantee that task is executed asynchronously, so it can throw right away catch (Exception ex) { ex.Data["ForEach.Index"] = itemIndex; context.OnOperationComplete(ex); } if (itemActionTask != null) { var capturedItemIndex = itemIndex; #pragma warning disable CS4014 // Justification: not awaited by design itemActionTask.ContinueWith( task => { Exception ex = null; if (task.IsFaulted) { ex = task.Exception; if (ex is AggregateException aggEx && aggEx.InnerExceptions.Count == 1) { ex = aggEx.InnerException; } ex.Data["ForEach.Index"] = capturedItemIndex; } context.OnOperationComplete(ex); }); #pragma warning restore CS4014 } itemIndex++; } } catch (Exception ex) { context.AddException(ex); } finally { await enumerator.DisposeAsync().ConfigureAwait(false); context.OnOperationComplete(); } }); return(context.CompletionTask); }
public static async Task ForEachAsync <T>(IEnumerable <T> container, Action <T> action) { ThrowIfInvalidForEachArgument(container, action); await ParallelForEachContext <T> .ForEachAsync(container, action, -1); }