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)); } }
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); }
/// <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"); } }