/// <summary>Initializes the target core.</summary> /// <param name="owningTarget">The target using this helper.</param> /// <param name="callAction">An action to invoke for all accepted items.</param> /// <param name="reorderingBuffer">The reordering buffer used by the owner; may be null.</param> /// <param name="dataflowBlockOptions">The options to use to configure this block. The target core assumes these options are immutable.</param> /// <param name="targetCoreOptions">Options for how the target core should behave.</param> internal TargetCore( ITargetBlock <TInput> owningTarget, Action <KeyValuePair <TInput, long> > callAction, IReorderingBuffer reorderingBuffer, ExecutionDataflowBlockOptions dataflowBlockOptions, TargetCoreOptions targetCoreOptions) { // Validate internal arguments Debug.Assert(owningTarget != null, "Core must be associated with a target block."); Debug.Assert(dataflowBlockOptions != null, "Options must be provided to configure the core."); Debug.Assert(callAction != null, "Action to invoke for each item is required."); // Store arguments and do additional initialization _owningTarget = owningTarget; _callAction = callAction; _reorderingBuffer = reorderingBuffer; _dataflowBlockOptions = dataflowBlockOptions; _targetCoreOptions = targetCoreOptions; _messages = (dataflowBlockOptions.MaxDegreeOfParallelism == 1) ? (IProducerConsumerQueue <KeyValuePair <TInput, long> >) new SingleProducerSingleConsumerQueue <KeyValuePair <TInput, long> >() : (IProducerConsumerQueue <KeyValuePair <TInput, long> >) new MultiProducerMultiConsumerQueue <KeyValuePair <TInput, long> >(); if (_dataflowBlockOptions.BoundedCapacity != System.Threading.Tasks.Dataflow.DataflowBlockOptions.Unbounded) { Debug.Assert(_dataflowBlockOptions.BoundedCapacity > 0, "Positive bounding count expected; should have been verified by options ctor"); _boundingState = new BoundingStateWithPostponed <TInput>(_dataflowBlockOptions.BoundedCapacity); } }
private void Initialize( Action <KeyValuePair <TInput, long> > processMessageAction, ExecutionDataflowBlockOptions dataflowBlockOptions, [NotNull] ref SourceCore <TOutput>?source, [NotNull] ref TargetCore <TInput>?target, ref ReorderingBuffer <IEnumerable <TOutput> >?reorderingBuffer, TargetCoreOptions targetCoreOptions) { if (dataflowBlockOptions == null) { throw new ArgumentNullException(nameof(dataflowBlockOptions)); } // Ensure we have options that can't be changed by the caller dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // Initialize onItemsRemoved delegate if necessary Action <ISourceBlock <TOutput>, int>?onItemsRemoved = null; if (dataflowBlockOptions.BoundedCapacity > 0) { onItemsRemoved = (owningSource, count) => ((TransformManyBlock <TInput, TOutput>)owningSource)._target.ChangeBoundingCount(-count); } // Initialize source component source = new SourceCore <TOutput>(this, dataflowBlockOptions, owningSource => ((TransformManyBlock <TInput, TOutput>)owningSource)._target.Complete(exception: null, dropPendingMessages: true), onItemsRemoved); // If parallelism is employed, we will need to support reordering messages that complete out-of-order. // However, a developer can override this with EnsureOrdered == false. if (dataflowBlockOptions.SupportsParallelExecution && dataflowBlockOptions.EnsureOrdered) { reorderingBuffer = new ReorderingBuffer <IEnumerable <TOutput> >( this, (source, messages) => ((TransformManyBlock <TInput, TOutput>)source)._source.AddMessages(messages)); } // Create the underlying target and source target = new TargetCore <TInput>(this, processMessageAction, _reorderingBuffer, dataflowBlockOptions, targetCoreOptions); // Link up the target half with the source half. In doing so, // ensure exceptions are propagated, and let the source know no more messages will arrive. // As the target has completed, and as the target synchronously pushes work // through the reordering buffer when async processing completes, // we know for certain that no more messages will need to be sent to the source. target.Completion.ContinueWith((completed, state) => { var sourceCore = (SourceCore <TOutput>)state !; if (completed.IsFaulted) { sourceCore.AddAndUnwrapAggregateException(completed.Exception !); } sourceCore.Complete(); }, source, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. // In those cases we need to fault the target half to drop its buffered messages and to release its // reservations. This should not create an infinite loop, because all our implementations are designed // to handle multiple completion requests and to carry over only one. source.Completion.ContinueWith((completed, state) => { var thisBlock = ((TransformManyBlock <TInput, TOutput>)state !) as IDataflowBlock; Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); thisBlock.Fault(completed.Exception !); }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); // Handle async cancellation requests by declining on the target Common.WireCancellationToComplete( dataflowBlockOptions.CancellationToken, Completion, state => ((TargetCore <TInput>)state !).Complete(exception: null, dropPendingMessages: true), target); DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } }