/* * /// <summary> * /// Awaits completion of all asynchronous evaluations. * /// </summary> * * public static IAwaitQuery<TResult> AwaitCompletion<T, TT, TResult>( * this IEnumerable<T> source, * Func<T, CancellationToken, Task<TT>> evaluator, * Func<T, TT, TResult> resultSelector, * Func<T, Exception, TResult> errorSelector, * Func<T, TResult> cancellationSelector) => * AwaitQuery.Create(options => * from e in source.AwaitCompletion(evaluator, (item, task) => (Item: item, Task: task)) * .WithOptions(options) * select e.Task.IsFaulted * ? errorSelector(e.Item, e.Task.Exception) * : e.Task.IsCanceled * ? cancellationSelector(e.Item) * : resultSelector(e.Item, e.Task.Result)); */ /// <summary> /// Awaits completion of all asynchronous evaluations irrespective of /// whether they succeed or fail. An additional argument specifies a /// function that projects the final result given the source item and /// completed task. /// </summary> /// <typeparam name="T">The type of the source elements.</typeparam> /// <typeparam name="TTaskResult"> The type of the tasks's result.</typeparam> /// <typeparam name="TResult">The type of the result elements.</typeparam> /// <param name="source">The source sequence.</param> /// <param name="evaluator">A function to begin the asynchronous /// evaluation of each element, the second parameter of which is a /// <see cref="CancellationToken"/> that can be used to abort /// asynchronous operations.</param> /// <param name="resultSelector">A fucntion that projects the final /// result given the source item and its asynchronous completion /// result.</param> /// <returns> /// A sequence query that stream its results as they are /// evaluated asynchronously. /// </returns> /// <remarks> /// <para> /// This method uses deferred execution semantics. The results are /// yielded as each asynchronous evaluation completes and, by default, /// not guaranteed to be based on the source sequence order. If order /// is important, compose further with /// <see cref="AsOrdered{T}"/>.</para> /// <para> /// This method starts a new task where the asynchronous evaluations /// take place and awaited. If the resulting sequence is partially /// consumed then there's a good chance that some projection work will /// be wasted and a cooperative effort is done that depends on the /// <paramref name="evaluator"/> function (via a /// <see cref="CancellationToken"/> as its second argument) to cancel /// those in flight.</para> /// <para> /// The <paramref name="evaluator"/> function should be designed to be /// thread-agnostic.</para> /// <para> /// The task returned by <paramref name="evaluator"/> should be started /// when the function is called (and not just a mere projection) /// otherwise changing concurrency options via /// <see cref="AsSequential{T}"/>, <see cref="MaxConcurrency{T}"/> or /// <see cref="UnboundedConcurrency{T}"/> will only change how many /// tasks are awaited at any given moment, not how many will be /// kept in flight. /// </para> /// </remarks> public static IAwaitQuery <TResult> AwaitCompletion <T, TTaskResult, TResult>( this IEnumerable <T> source, Func <T, CancellationToken, Task <TTaskResult> > evaluator, Func <T, Task <TTaskResult>, TResult> resultSelector) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (evaluator == null) { throw new ArgumentNullException(nameof(evaluator)); } return (AwaitQuery.Create( options => _(options.MaxConcurrency ?? int.MaxValue, options.Scheduler ?? TaskScheduler.Default, options.PreserveOrder))); IEnumerable <TResult> _(int maxConcurrency, TaskScheduler scheduler, bool ordered) { var notices = new BlockingCollection <(Notice, (int, T, Task <TTaskResult>), ExceptionDispatchInfo)>(); var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var completed = false; var enumerator = source.Index() .Select(e => (e.Key, Item: e.Value, Task: evaluator(e.Value, cancellationToken))) .GetEnumerator(); IDisposable disposable = enumerator; // disables AccessToDisposedClosure warnings try { Task.Factory.StartNew( () => CollectToAsync( enumerator, e => e.Task, notices, (e, r) => (Notice.Result, (e.Key, e.Item, e.Task), default),
/* * /// <summary> * /// Awaits completion of all asynchronous evaluations. * /// </summary> * * public static IAwaitQuery<TResult> AwaitCompletion<T, TT, TResult>( * this IEnumerable<T> source, * Func<T, CancellationToken, Task<TT>> evaluator, * Func<T, TT, TResult> resultSelector, * Func<T, Exception, TResult> errorSelector, * Func<T, TResult> cancellationSelector) => * AwaitQuery.Create(options => * from e in source.AwaitCompletion(evaluator, (item, task) => (Item: item, Task: task)) * .WithOptions(options) * select e.Task.IsFaulted * ? errorSelector(e.Item, e.Task.Exception) * : e.Task.IsCanceled * ? cancellationSelector(e.Item) * : resultSelector(e.Item, e.Task.Result)); */ /// <summary> /// Awaits completion of all asynchronous evaluations irrespective of /// whether they succeed or fail. An additional argument specifies a /// function that projects the final result given the source item and /// completed task. /// </summary> /// <typeparam name="T">The type of the source elements.</typeparam> /// <typeparam name="TTaskResult"> The type of the tasks's result.</typeparam> /// <typeparam name="TResult">The type of the result elements.</typeparam> /// <param name="source">The source sequence.</param> /// <param name="evaluator">A function to begin the asynchronous /// evaluation of each element, the second parameter of which is a /// <see cref="CancellationToken"/> that can be used to abort /// asynchronous operations.</param> /// <param name="resultSelector">A fucntion that projects the final /// result given the source item and its asynchronous completion /// result.</param> /// <returns> /// A sequence query that stream its results as they are /// evaluated asynchronously. /// </returns> /// <remarks> /// <para> /// This method uses deferred execution semantics. The results are /// yielded as each asynchronous evaluation completes and, by default, /// not guaranteed to be based on the source sequence order. If order /// is important, compose further with /// <see cref="AsOrdered{T}"/>.</para> /// <para> /// This method starts a new task where the asynchronous evaluations /// take place and awaited. If the resulting sequence is partially /// consumed then there's a good chance that some projection work will /// be wasted and a cooperative effort is done that depends on the /// <paramref name="evaluator"/> function (via a /// <see cref="CancellationToken"/> as its second argument) to cancel /// those in flight.</para> /// <para> /// The <paramref name="evaluator"/> function should be designed to be /// thread-agnostic.</para> /// <para> /// The task returned by <paramref name="evaluator"/> should be started /// when the function is called (and not just a mere projection) /// otherwise changing concurrency options via /// <see cref="AsSequential{T}"/>, <see cref="MaxConcurrency{T}"/> or /// <see cref="UnboundedConcurrency{T}"/> will only change how many /// tasks are awaited at any given moment, not how many will be /// kept in flight. /// </para> /// </remarks> public static IAwaitQuery <TResult> AwaitCompletion <T, TTaskResult, TResult>( this IEnumerable <T> source, Func <T, CancellationToken, Task <TTaskResult> > evaluator, Func <T, Task <TTaskResult>, TResult> resultSelector) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (evaluator == null) { throw new ArgumentNullException(nameof(evaluator)); } return (AwaitQuery.Create( options => _(options.MaxConcurrency, options.Scheduler ?? TaskScheduler.Default, options.PreserveOrder))); IEnumerable <TResult> _(int?maxConcurrency, TaskScheduler scheduler, bool ordered) { // A separate task will enumerate the source and launch tasks. // It will post all progress as notices to the collection below. // A notice is essentially a discriminated union like: // // type Notice<'a, 'b> = // | End // | Result of (int * 'a * Task<'b>) // | Error of ExceptionDispatchInfo // // Note that BlockingCollection.CompleteAdding is never used to // to mark the end (which its own notice above) because // BlockingCollection.Add throws if called after CompleteAdding // and we want to deliberately tolerate the race condition. var notices = new BlockingCollection <(Notice, (int, T, Task <TTaskResult>), ExceptionDispatchInfo)>(); var consumerCancellationTokenSource = new CancellationTokenSource(); (Exception, Exception)lastCriticalErrors = default; void PostNotice(Notice notice, (int, T, Task <TTaskResult>) item, Exception error) { // If a notice fails to post then assume critical error // conditions (like low memory), capture the error without // further allocation of resources and trip the cancellation // token source used by the main loop waiting on notices. // Note that only the "last" critical error is reported // as maintaining a list would incur allocations. The idea // here is to make a best effort attempt to report any of // the error conditions that may be occuring, which is still // better than nothing. try { var edi = error != null ? ExceptionDispatchInfo.Capture(error) : null; notices.Add((notice, item, edi)); } catch (Exception e) { // Don't use ExceptionDispatchInfo.Capture here to avoid // inducing allocations if already under low memory // conditions. lastCriticalErrors = (e, error); consumerCancellationTokenSource.Cancel(); throw; } } var completed = false; var cancellationTokenSource = new CancellationTokenSource(); var enumerator = source.Index().GetEnumerator(); IDisposable disposable = enumerator; // disables AccessToDisposedClosure warnings try { var cancellationToken = cancellationTokenSource.Token; // Fire-up a parallel loop to iterate through the source and // launch tasks, posting a result-notice as each task // completes and another, an end-notice, when all tasks have // completed. Task.Factory.StartNew( async() => { try { await enumerator.StartAsync( e => evaluator(e.Value, cancellationToken), (e, r) => PostNotice(Notice.Result, (e.Key, e.Value, r), default), () => PostNotice(Notice.End, default, default),
/// <summary> /// Creates a sequence query that streams the result of each task in /// the source sequence as it completes asynchronously. A /// <see cref="CancellationToken"/> is passed for each asynchronous /// evaluation to abort any asynchronous operations in flight if the /// sequence is not fully iterated. /// </summary> /// <typeparam name="T">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the result elements.</typeparam> /// <param name="source">The source sequence.</param> /// <param name="evaluator">A function to begin the asynchronous /// evaluation of each element, the second parameter of which is a /// <see cref="CancellationToken"/> that can be used to abort /// asynchronous operations.</param> /// <returns> /// A sequence query that stream its results as they are /// evaluated asynchronously. /// </returns> /// <remarks> /// <para> /// This method uses deferred execution semantics. The results are /// yielded as each asynchronous evaluation completes and, by default, /// not guaranteed to be based on the source sequence order. If order /// is important, compose further with /// <see cref="AsOrdered{T}"/>.</para> /// <para> /// This method starts a new task where the asynchronous evaluations /// take place and awaited. If the resulting sequence is partially /// consumed then there's a good chance that some projection work will /// be wasted and a cooperative effort is done that depends on the /// <paramref name="evaluator"/> function (via a /// <see cref="CancellationToken"/> as its second argument) to cancel /// those in flight.</para> /// <para> /// The <paramref name="evaluator"/> function should be designed to be /// thread-agnostic.</para> /// <para> /// The task returned by <paramref name="evaluator"/> should be started /// when the function is called (and not just a mere projection) /// otherwise changing concurrency options via /// <see cref="AsSequential{T}"/>, <see cref="MaxConcurrency{T}"/> or /// <see cref="UnboundedConcurrency{T}"/> will only change how many /// tasks are awaited at any given moment, not how many will be /// kept in flight. /// </para> /// </remarks> public static IAwaitQuery <TResult> Await <T, TResult>( this IEnumerable <T> source, Func <T, CancellationToken, Task <TResult> > evaluator) => AwaitQuery.Create(options => from t in source.AwaitCompletion(evaluator, (_, t) => t) .WithOptions(options) select t.GetAwaiter().GetResult());
/* * /// <summary> * /// Awaits completion of all asynchronous evaluations. * /// </summary> * * public static IAwaitQuery<TResult> AwaitCompletion<T, TT, TResult>( * this IEnumerable<T> source, * Func<T, CancellationToken, Task<TT>> evaluator, * Func<T, TT, TResult> resultSelector, * Func<T, Exception, TResult> errorSelector, * Func<T, TResult> cancellationSelector) => * AwaitQuery.Create(options => * from e in source.AwaitCompletion(evaluator, (item, task) => (Item: item, Task: task)) * .WithOptions(options) * select e.Task.IsFaulted * ? errorSelector(e.Item, e.Task.Exception) * : e.Task.IsCanceled * ? cancellationSelector(e.Item) * : resultSelector(e.Item, e.Task.Result)); */ /// <summary> /// Awaits completion of all asynchronous evaluations irrespective of /// whether they succeed or fail. An additional argument specifies a /// function that projects the final result given the source item and /// completed task. /// </summary> /// <typeparam name="T">The type of the source elements.</typeparam> /// <typeparam name="TTaskResult"> The type of the tasks's result.</typeparam> /// <typeparam name="TResult">The type of the result elements.</typeparam> /// <param name="source">The source sequence.</param> /// <param name="evaluator">A function to begin the asynchronous /// evaluation of each element, the second parameter of which is a /// <see cref="CancellationToken"/> that can be used to abort /// asynchronous operations.</param> /// <param name="resultSelector">A fucntion that projects the final /// result given the source item and its asynchronous completion /// result.</param> /// <returns> /// A sequence query that stream its results as they are /// evaluated asynchronously. /// </returns> /// <remarks> /// <para> /// This method uses deferred execution semantics. The results are /// yielded as each asynchronous evaluation completes and, by default, /// not guaranteed to be based on the source sequence order. If order /// is important, compose further with /// <see cref="AsOrdered{T}"/>.</para> /// <para> /// This method starts a new task where the asynchronous evaluations /// take place and awaited. If the resulting sequence is partially /// consumed then there's a good chance that some projection work will /// be wasted and a cooperative effort is done that depends on the /// <paramref name="evaluator"/> function (via a /// <see cref="CancellationToken"/> as its second argument) to cancel /// those in flight.</para> /// <para> /// The <paramref name="evaluator"/> function should be designed to be /// thread-agnostic.</para> /// <para> /// The task returned by <paramref name="evaluator"/> should be started /// when the function is called (and not just a mere projection) /// otherwise changing concurrency options via /// <see cref="AsSequential{T}"/>, <see cref="MaxConcurrency{T}"/> or /// <see cref="UnboundedConcurrency{T}"/> will only change how many /// tasks are awaited at any given moment, not how many will be /// kept in flight. /// </para> /// </remarks> public static IAwaitQuery <TResult> AwaitCompletion <T, TTaskResult, TResult>( this IEnumerable <T> source, Func <T, CancellationToken, Task <TTaskResult> > evaluator, Func <T, Task <TTaskResult>, TResult> resultSelector) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (evaluator == null) { throw new ArgumentNullException(nameof(evaluator)); } return (AwaitQuery.Create( options => _(options.MaxConcurrency, options.Scheduler ?? TaskScheduler.Default, options.PreserveOrder))); IEnumerable <TResult> _(int?maxConcurrency, TaskScheduler scheduler, bool ordered) { // A separate task will enumerate the source and launch tasks. // It will post all progress as notices to the collection below. // A notice is essentially a discriminated union like: // // type Notice<'a, 'b> = // | End // | Result of (int * 'a * Task<'b>) // | Error of ExceptionDispatchInfo // // Note that BlockingCollection.CompleteAdding is never used to // to mark the end (which its own notice above) because // BlockingCollection.Add throws if called after CompleteAdding // and we want to deliberately tolerate the race condition. var notices = new BlockingCollection <(Notice, (int, T, Task <TTaskResult>), ExceptionDispatchInfo?)>(); var consumerCancellationTokenSource = new CancellationTokenSource(); (Exception?, Exception?)lastCriticalErrors = default; var completed = false; var cancellationTokenSource = new CancellationTokenSource(); var enumerator = source.Index().GetEnumerator(); IDisposable disposable = enumerator; // disables AccessToDisposedClosure warnings try { var cancellationToken = cancellationTokenSource.Token; // Fire-up a parallel loop to iterate through the source and // launch tasks, posting a result-notice as each task // completes and another, an end-notice, when all tasks have // completed. Task.Factory.StartNew( async() => { try { await enumerator.StartAsync( e => evaluator(e.Value, cancellationToken), (e, r) => PostNotice(Notice.Result, (e.Key, e.Value, r), default), () => PostNotice(Notice.End, default, default),