Ejemplo n.º 1
0
        /*
         * /// <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),
Ejemplo n.º 2
0
        /*
         * /// <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),