Example #1
0
        /// <summary>Run the pump until the inner iterator is done, an error occurs, or the cancellation token is fired</summary>
        public async Task PumpAsync(bool stopOnFirstError, CancellationToken cancellationToken)
        {
            if (m_state != STATE_IDLE)
            {
                // either way, we need to stop !
                Exception error;

                if (m_state == STATE_DISPOSED)
                {
                    error = new ObjectDisposedException(null, "Pump has already been disposed");
                }
                else if (m_state >= STATE_FAILED)
                {
                    error = new InvalidOperationException("Pump has already completed once");
                }
                else
                {
                    error = new InvalidOperationException("Pump is already running");
                }

                try
                {
                    m_target.OnError(ExceptionDispatchInfo.Capture(error));
                }
                catch
                {
                    m_target.OnCompleted();
                }

                throw error;
            }

            try
            {
                LogPump("Starting pump");

                while (!cancellationToken.IsCancellationRequested && m_state != STATE_DISPOSED)
                {
                    LogPump("Waiting for next");
                    m_state = STATE_WAITING_FOR_NEXT;
                    var current = await m_source.ReceiveAsync(cancellationToken).ConfigureAwait(false);

                    LogPump("Received " + (current.HasValue ? "value" : current.HasFailed ? "error" : "completion") + ", publishing...");
                    m_state = STATE_PUBLISHING_TO_TARGET;

                    await m_target.Publish(current, cancellationToken).ConfigureAwait(false);

                    if (current.HasFailed && stopOnFirstError)
                    {
                        m_state = STATE_FAILED;
                        LogPump("Stopping after this error");
                        current.ThrowForNonSuccess();
                    }
                    else if (current.IsEmpty)
                    {
                        m_state = STATE_DONE;
                        LogPump("Completed");
                        return;
                    }
                }

                // push the cancellation on the queue, and throw
                throw new OperationCanceledException(cancellationToken);
            }
            catch (Exception e)
            {
                LogPump("Failed " + e.Message);

                switch (m_state)
                {
                case STATE_WAITING_FOR_NEXT:
                {                         // push the info to the called
                    try
                    {
                        m_target.OnError(ExceptionDispatchInfo.Capture(e));
                    }
                    catch (Exception x)
                    {
                        LogPump("Failed to notify target of error: " + x.Message);
                        throw;
                    }
                    break;
                }

                case STATE_PUBLISHING_TO_TARGET:           // the error comes from the target itself, push back to caller!
                case STATE_FAILED:                         // we want to notify the caller of some problem
                {
                    throw;
                }
                }
            }
            finally
            {
                if (m_state != STATE_DISPOSED)
                {
                    m_target.OnCompleted();
                }
                LogPump("Stopped pump");
            }
        }
        /// <summary>Consumes all the elements of the source, and publish them to the target, one by one and in order</summary>
        /// <param name="source">Source that produces elements asynchronously</param>
        /// <param name="target">Target that consumes elements asynchronously</param>
        /// <param name="ct">Cancellation token</param>
        /// <returns>Task that completes when all the elements of the source have been published to the target, or fails if on the first error, or the token is cancelled unexpectedly</returns>
        /// <remarks>The pump will only read one element at a time, and wait for it to be published to the target, before reading the next element.</remarks>
        public static async Task PumpToAsync <T>(this IAsyncSource <T> source, IAsyncTarget <T> target, CancellationToken ct)
        {
            ct.ThrowIfCancellationRequested();

            bool notifiedCompletion = false;
            bool notifiedError      = false;

            try
            {
                //LogPump("Starting pump");

                while (!ct.IsCancellationRequested)
                {
                    //LogPump("Waiting for next");

                    var current = await source.ReceiveAsync(ct).ConfigureAwait(false);

                    //LogPump("Received " + (current.HasValue ? "value" : current.Failed ? "error" : "completion") + ", publishing... " + current);
                    if (ct.IsCancellationRequested)
                    {
                        // REVIEW: should we notify the target?
                        // REVIEW: if the item is IDisposble, who will clean up?
                        break;
                    }

                    // push the data/error/completion on to the target, which will triage and update its state accordingly
                    await target.Publish(current, ct).ConfigureAwait(false);

                    if (current.Failed)
                    {                                 // bounce the error back to the caller
                                                      //REVIEW: SHOULD WE? We poush the error to the target, and the SAME error to the caller... who should be responsible for handling it?
                                                      // => target should know about the error (to cancel something)
                                                      // => caller should maybe also know that the pump failed unexpectedly....
                        notifiedError = true;
                        current.ThrowForNonSuccess(); // throws an exception right here
                        return;                       // should not be reached
                    }
                    else if (current.IsEmpty)
                    {                     // the source has completed, stop the pump
                                          //LogPump("Completed");
                        notifiedCompletion = true;
                        return;
                    }
                }

                // notify cancellation if it happend while we were pumping
                if (ct.IsCancellationRequested)
                {
                    //LogPump("We were cancelled!");
                    throw new OperationCanceledException(ct);
                }
            }
            catch (Exception e)
            {
                //LogPump("Failed: " + e);

                if (!notifiedCompletion && !notifiedError)
                {                 // notify the target that we crashed while fetching the next
                    try
                    {
                        //LogPump("Push error down to target: " + e.Message);
                        target.OnError(ExceptionDispatchInfo.Capture(e));
                        notifiedError = true;
                    }
                    catch (Exception x) when(!x.IsFatalError())
                    {
                        //LogPump("Failed to notify target of error: " + x.Message);
                    }
                }

                throw;
            }
            finally
            {
                if (!notifiedCompletion)
                {                 // we must be sure to complete the target if we haven't done so yet!
                    //LogPump("Notify target of completion due to unexpected conditions");
                    target.OnCompleted();
                }
                //LogPump("Stopped pump");
            }
        }