public Task OnNextAsync(T value, CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(TaskHelpers.CompletedTask);
            }

            if (m_done)
            {
                throw new InvalidOperationException("Cannot send any more values because this transform has already completed");
            }

            try
            {
                // we start the task here, but do NOT wait for its completion!
                // It is the job of the target to handle that (and ordering)
                Task <R> task;
                if (m_scheduler == null)
                {                 // execute inline
                    task = m_transform(value, cancellationToken);
                }
                else
                {                 // execute in a scheduler
                    task = Task.Factory.StartNew(
                        (state) =>
                    {
                        var prms = (Tuple <AsyncTransform <T, R>, T, CancellationToken>)state;
                        return(prms.Item1.m_transform(prms.Item2, prms.Item3));
                    },
                        Tuple.Create(this, value, cancellationToken),
                        cancellationToken,
                        TaskCreationOptions.PreferFairness,
                        m_scheduler
                        ).Unwrap();
                }

                return(m_target.OnNextAsync(task, cancellationToken));
            }
            catch (Exception e)
            {
#if NET_4_0
                m_target.OnError(e);
#else
                m_target.OnError(ExceptionDispatchInfo.Capture(e));
#endif
                return(TaskHelpers.FromException <object>(e));
            }
        }
Example #2
0
 private void OnError(ExceptionDispatchInfo e)
 {
     try
     {
         m_state = STATE_FAILED;
         m_target.OnError(e);
     }
     catch (Exception x)
     {
         LogDebug("failed to push error to target: " + x.Message);
         //TODO ?
     }
 }
        /// <summary>Publish a new result on this async target, by correclty handling success, termination and failure</summary>
        public static Task Publish <T>(this IAsyncTarget <T> target, Maybe <T> result, CancellationToken ct)
        {
            Contract.Requires(target != null);

            if (ct.IsCancellationRequested)
            {
                return(Task.FromCanceled(ct));
            }

            if (result.HasValue)
            {             // we have the next value
                return(target.OnNextAsync(result.Value, ct));
            }

            if (result.Failed)
            {             // we have failed
                target.OnError(result.CapturedError);
                return(Task.CompletedTask);
            }

            // this is the end of the stream
            target.OnCompleted();
            return(Task.CompletedTask);
        }
Example #4
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");
            }
        }