/// <summary>Processes the message with a user-provided transform function that returns an observable.</summary> /// <param name="function">The transform function to use to process the message.</param> /// <param name="messageWithId">The message to be processed.</param> private void ProcessMessageWithTask(Func <TInput, Task <IEnumerable <TOutput> > > function, KeyValuePair <TInput, long> messageWithId) { Debug.Assert(function != null, "Function to invoke is required."); // Run the transform function to get the resulting task Task <IEnumerable <TOutput> >?task = null; Exception?caughtException = null; try { task = function(messageWithId.Key); } catch (Exception exc) { caughtException = exc; } // If no task is available, either because null was returned or an exception was thrown, we're done. if (task == null) { // If we didn't get a task because an exception occurred, store it // (or if the exception was cancellation, just ignore it). if (caughtException != null && !Common.IsCooperativeCancellation(caughtException)) { Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key); _target.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); } // Notify that we're done with this input and that we got no output for the input. if (_reorderingBuffer != null) { // If there's a reordering buffer, "store" an empty output. This will // internally both update the output buffer and decrement the bounding count // accordingly. StoreOutputItems(messageWithId, null); _target.SignalOneAsyncMessageCompleted(); } else { // As a fast path if we're not reordering, decrement the bounding // count as part of our signaling that we're done, since this will // internally take the lock only once, whereas the above path will // take the lock twice. _target.SignalOneAsyncMessageCompleted(boundingCountChange: -1); } return; } // We got back a task. Now wait for it to complete and store its results. // Unlike with TransformBlock and ActionBlock, We run the continuation on the user-provided // scheduler as we'll be running user code through enumerating the returned enumerable. task.ContinueWith((completed, state) => { var tuple = (Tuple <TransformManyBlock <TInput, TOutput>, KeyValuePair <TInput, long> >)state !; tuple.Item1.AsyncCompleteProcessMessageWithTask(completed, tuple.Item2); }, Tuple.Create(this, messageWithId), CancellationToken.None, Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), _source.DataflowBlockOptions.TaskScheduler); }
/// <summary>Processes the message with a user-provided action that returns a task.</summary> /// <param name="action">The action to use to process the message.</param> /// <param name="messageWithId">The message to be processed.</param> private void ProcessMessageWithTask(Func <TInput, Task> action, KeyValuePair <TInput, long> messageWithId) { Debug.Assert(action != null, "action needed for processing"); Debug.Assert(_defaultTarget != null); // Run the action to get the task that represents the operation's completion Task? task = null; Exception?caughtException = null; try { task = action(messageWithId.Key); } catch (Exception exc) { caughtException = exc; } // If no task is available, we're done. if (task == null) { // If we didn't get a task because an exception occurred, // store it (if the exception was cancellation, just ignore it). if (caughtException != null && !Common.IsCooperativeCancellation(caughtException)) { Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key); _defaultTarget.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); } // Signal that we're done this async operation. _defaultTarget.SignalOneAsyncMessageCompleted(boundingCountChange: -1); return; } else if (task.IsCompleted) { AsyncCompleteProcessMessageWithTask(task); } else { // Otherwise, join with the asynchronous operation when it completes. task.ContinueWith((completed, state) => { ((ActionBlock <TInput>)state !).AsyncCompleteProcessMessageWithTask(completed); }, this, CancellationToken.None, Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default); } }