public void OnCompleted() { if (!m_done) { m_done = true; m_target.OnCompleted(); } }
/// <summary>Run the pump until the inner iterator is done, an error occurs, or the cancellation token is fired</summary> public async Task PumpAsync(CancellationToken ct) { if (m_state != STATE_IDLE) { if (m_state >= STATE_FAILED) { throw new InvalidOperationException("The iterator pump has already completed once"); } else { throw new InvalidOperationException("The iterator pump is already running"); } } try { while (!ct.IsCancellationRequested) { LogDebug("waiting for next"); m_state = STATE_WAITING_FOR_NEXT; if (!(await m_iterator.MoveNext(ct).ConfigureAwait(false))) { LogDebug("completed"); m_state = STATE_DONE; m_target.OnCompleted(); return; } LogDebug("got item, publishing..."); m_state = STATE_PUBLISHING_TO_TARGET; await m_target.OnNextAsync(m_iterator.Current, ct).ConfigureAwait(false); } // push the cancellation on the queue OnError(ExceptionDispatchInfo.Capture(new OperationCanceledException(ct))); // and throw! } catch (Exception e) { LogDebug("failed... (" + m_state + ") : " + e.Message); if (m_state == STATE_FAILED) { // already signaled the target, just throw throw; } // push the error on the queue, and eat the error OnError(ExceptionDispatchInfo.Capture(e)); } finally { if (m_state != STATE_FAILED) { m_state = STATE_DONE; } LogDebug("stopped (" + m_state + ")"); } }
/// <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); }
/// <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"); } }