private void ScheduleConsumerIfNecessary(bool isReplica) { // If there's currently no active task... if (_activeConsumer == null) { // Create a new consumption task and try to set it as current as long as there's still no other task var newConsumer = new Task( state => ((SpscTargetCore <TInput>)state).ProcessMessagesLoopCore(), this, CancellationToken.None, Common.GetCreationOptionsForTask(isReplica)); if (Interlocked.CompareExchange(ref _activeConsumer, newConsumer, null) == null) { // We won the race. This task is now the consumer. #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.TaskLaunchedForMessageHandling( _owningTarget, newConsumer, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, _messages.Count); } #endif // Start the task. In the erroneous case where the scheduler throws an exception, // just allow it to propagate. Our other option would be to fault the block with // that exception, but in order for the block to complete we need to schedule a consumer // task to do so, and it's very likely that if the scheduler is throwing an exception // now, it would do so again. newConsumer.Start(_dataflowBlockOptions.TaskScheduler); } } }
/// <summary>Adds a target to the registry.</summary> /// <param name="target">The target to add.</param> /// <param name="linkOptions">The link options.</param> internal void Add(ref ITargetBlock <T> target, DataflowLinkOptions linkOptions) { Contract.Requires(target != null, "The target that is supposed to be linked must not be null."); Contract.Requires(linkOptions != null, "The link options must not be null."); LinkedTargetInfo targetInfo; // If the target already exists in the registry, replace it with a new NopLinkPropagator to maintain uniqueness if (_targetInformation.TryGetValue(target, out targetInfo)) { target = new NopLinkPropagator(_owningSource, target); } // Add the target to both stores, the list and the dictionary, which are used for different purposes var node = new LinkedTargetInfo(target, linkOptions); AddToList(node, linkOptions.Append); _targetInformation.Add(target, node); // Increment the optimization counter if needed Contract.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time."); if (node.RemainingMessages > 0) { _linksWithRemainingMessages++; } #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockLinking(_owningSource, target); } #endif }
private void ProcessAsyncIfNecessary_Slow(bool repeat) { Debug.Assert(HasRoomForMoreServiceTasks, "There must be room to process asynchronously."); Common.ContractAssertMonitorStatus(IncomingLock, held: true); // Determine preconditions to launching a processing task bool messagesAvailableOrPostponed = !_messages.IsEmpty || (!_decliningPermanently && _boundingState != null && _boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count > 0); // If all conditions are met, launch away if (messagesAvailableOrPostponed && !CanceledOrFaulted) { // Any book keeping related to the processing task like incrementing the // DOP counter or eventually recording the tasks reference must be done // before the task starts. That is because the task itself will do the // reverse operation upon its completion. _numberOfOutstandingOperations++; if (UsesAsyncCompletion) { _numberOfOutstandingServiceTasks++; } var taskForInputProcessing = new Task(thisTargetCore => ((TargetCore <TInput>)thisTargetCore).ProcessMessagesLoopCore(), this, Common.GetCreationOptionsForTask(repeat)); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.TaskLaunchedForMessageHandling( _owningTarget, taskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, _messages.Count + (_boundingState != null ? _boundingState.PostponedMessages.Count : 0)); } #endif // Start the task handling scheduling exceptions Exception exception = Common.StartTaskSafe(taskForInputProcessing, _dataflowBlockOptions.TaskScheduler); if (exception != null) { // Get out from under currently held locks. Complete re-acquires the locks it needs. Task.Factory.StartNew(exc => Complete(exception: (Exception)exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false, revertProcessingState: true), exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); } } }
/// <summary>Actually removes the target from the registry.</summary> /// <param name="target">The target to remove.</param> /// <param name="onlyIfReachedMaxMessages"> /// Only remove the target if it's configured to be unlinked after one propagation. /// </param> private void Remove_Slow(ITargetBlock <T> target, bool onlyIfReachedMaxMessages) { Contract.Requires(target != null, "Target to remove is required."); // Make sure we've intended to go the slow route Contract.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time."); Contract.Assert(!onlyIfReachedMaxMessages || _linksWithRemainingMessages > 0, "We shouldn't have ended on the slow path."); // If the target is registered... LinkedTargetInfo node; if (_targetInformation.TryGetValue(target, out node)) { Contract.Assert(node != null, "The LinkedTargetInfo node referenced in the Dictionary must be non-null."); // Remove the target, if either there's no constraint on the removal // or if this was the last remaining message. if (!onlyIfReachedMaxMessages || node.RemainingMessages == 1) { RemoveFromList(node); _targetInformation.Remove(target); // Decrement the optimization counter if needed if (node.RemainingMessages == 0) { _linksWithRemainingMessages--; } Contract.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time."); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockUnlinking(_owningSource, target); } #endif } // If the target is to stay and we are counting the remaining messages for this link, decrement the counter else if (node.RemainingMessages > 0) { Contract.Assert(node.RemainingMessages > 1, "The target should have been removed, because there are no remaining messages."); node.RemainingMessages--; } } }
/// <summary> /// Completes the block. This must only be called once, and only once all of the completion conditions are met. /// </summary> private void CompleteBlockOncePossible() { Debug.Assert(_completionReserved, "Should only invoke once completion has been reserved."); // Dump any messages that might remain in the queue, which could happen if we completed due to exceptions. TInput dumpedMessage; while (_messages.TryDequeue(out dumpedMessage)) { ; } // Complete the completion task bool result; if (_exceptions != null) { Exception[] exceptions; lock (_exceptions) exceptions = _exceptions.ToArray(); result = CompletionSource.TrySetException(exceptions); } else { result = CompletionSource.TrySetResult(default(VoidResult)); } Debug.Assert(result, "Expected completion task to not yet be completed"); // We explicitly do not set the _activeTask to null here, as that would // allow for races where a producer calling OfferMessage could end up // seeing _activeTask as null and queueing a new consumer task even // though the block has completed. #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCompleted(_owningTarget); } #endif }