/// <summary>Called when postponed messages may need to be consumed.</summary> /// <param name="isReplacementReplica">Whether this call is the continuation of a previous message loop.</param> internal void ConsumeAsyncIfNecessary(bool isReplacementReplica = false) { Common.ContractAssertMonitorStatus(IncomingLock, held: true); Debug.Assert(_boundingState != null, "Must be in bounded mode."); if (!_targetDecliningPermanently && _boundingState.TaskForInputProcessing == null && _boundingState.PostponedMessages.Count > 0 && _boundingState.CountIsLessThanBound) { // Create task and store into _taskForInputProcessing prior to scheduling the task // so that _taskForInputProcessing will be visibly set in the task loop. _boundingState.TaskForInputProcessing = new Task(state => ((BufferBlock <T>)state !).ConsumeMessagesLoopCore(), this, Common.GetCreationOptionsForTask(isReplacementReplica)); DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.TaskLaunchedForMessageHandling( this, _boundingState.TaskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, _boundingState.PostponedMessages.Count); } // Start the task handling scheduling exceptions Exception?exception = Common.StartTaskSafe(_boundingState.TaskForInputProcessing, _source.DataflowBlockOptions.TaskScheduler); if (exception != null) { // Get out from under currently held locks. CompleteCore re-acquires the locks it needs. Task.Factory.StartNew(exc => CompleteCore(exception: (Exception)exc !, storeExceptionEvenIfAlreadyCompleting: true, revertProcessingState: true), exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); } } }
/// <summary>Initializes the <see cref="BufferBlock{T}"/> with the specified <see cref="DataflowBlockOptions"/>.</summary> /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BufferBlock{T}"/>.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception> public BufferBlock(DataflowBlockOptions dataflowBlockOptions) { if (dataflowBlockOptions == null) { throw new ArgumentNullException("dataflowBlockOptions"); } Contract.EndContractBlock(); // Ensure we have options that can't be changed by the caller dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // Initialize bounding state if necessary Action <ISourceBlock <T>, int> onItemsRemoved = null; if (dataflowBlockOptions.BoundedCapacity > 0) { onItemsRemoved = (owningSource, count) => ((BufferBlock <T>)owningSource).OnItemsRemoved(count); _boundingState = new BoundingStateWithPostponedAndTask <T>(dataflowBlockOptions.BoundedCapacity); } // Initialize the source state _source = new SourceCore <T>(this, dataflowBlockOptions, owningSource => ((BufferBlock <T>)owningSource).Complete(), onItemsRemoved); // 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. #if NET_4_0_ABOVE _source.Completion.ContinueWith((completed, state) => { var thisBlock = ((BufferBlock <T>)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); #else Action <Task> continuationAction = completed => { var thisBlock = this as IDataflowBlock; Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); thisBlock.Fault(completed.Exception); }; _source.Completion.ContinueWith(continuationAction, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); #endif // Handle async cancellation requests by declining on the target Common.WireCancellationToComplete( dataflowBlockOptions.CancellationToken, _source.Completion, owningSource => ((BufferBlock <T>)owningSource).Complete(), this); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif }
/// <summary>Initializes the <see cref="WriteOnceBlock{T}"/> with the specified <see cref="DataflowBlockOptions"/>.</summary> /// <param name="cloningFunction"> /// The function to use to clone the data when offered to other blocks. /// This may be null to indicate that no cloning need be performed. /// </param> /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="WriteOnceBlock{T}"/>.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception> public WriteOnceBlock(Func <T, T> cloningFunction, DataflowBlockOptions dataflowBlockOptions) { // Validate arguments if (dataflowBlockOptions == null) { throw new ArgumentNullException("dataflowBlockOptions"); } Contract.EndContractBlock(); // Store the option _cloningFunction = cloningFunction; _dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // The target registry also serves as our ValueLock, // and thus must always be initialized, even if the block is pre-canceled, as // subsequent usage of the block may run through code paths that try to take this lock. _targetRegistry = new TargetRegistry <T>(this); // If a cancelable CancellationToken has been passed in, // we need to initialize the completion task's TCS now. if (dataflowBlockOptions.CancellationToken.CanBeCanceled) { _lazyCompletionTaskSource = new TaskCompletionSource <VoidResult>(); // If we've already had cancellation requested, do as little work as we have to // in order to be done. if (dataflowBlockOptions.CancellationToken.IsCancellationRequested) { _completionReserved = _decliningPermanently = true; // Cancel the completion task's TCS _lazyCompletionTaskSource.SetCanceled(); } else { // Handle async cancellation requests by declining on the target Common.WireCancellationToComplete( dataflowBlockOptions.CancellationToken, _lazyCompletionTaskSource.Task, state => ((WriteOnceBlock <T>)state).Complete(), this); } } #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif }
/// <summary>Completes the block.</summary> /// <remarks> /// This is called only once. /// </remarks> private void CompleteBlock(IList <Exception> exceptions) { // Do not invoke the CompletionTaskSource property if there is a chance that _lazyCompletionTaskSource // has not been initialized yet and we may have to complete normally, because that would defeat the // sole purpose of the TCS being lazily initialized. Contract.Requires(_lazyCompletionTaskSource == null || !_lazyCompletionTaskSource.Task.IsCompleted, "The task completion source must not be completed. This must be the only thread that ever completes the block."); // Save the linked list of targets so that it could be traversed later to propagate completion TargetRegistry <T> .LinkedTargetInfo linkedTargets = _targetRegistry.ClearEntryPoints(); // Complete the block's completion task if (exceptions != null && exceptions.Count > 0) { CompletionTaskSource.TrySetException(exceptions); } else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested) { CompletionTaskSource.TrySetCanceled(); } else { // Safely try to initialize the completion task's TCS with a cached completed TCS. // If our attempt succeeds (CompareExchange returns null), we have nothing more to do. // If the completion task's TCS was already initialized (CompareExchange returns non-null), // we have to complete that TCS instance. if (Interlocked.CompareExchange(ref _lazyCompletionTaskSource, Common.CompletedVoidResultTaskCompletionSource, null) != null) { _lazyCompletionTaskSource.TrySetResult(default(VoidResult)); } } // Now that the completion task is completed, we may propagate completion to the linked targets _targetRegistry.PropagateCompletion(linkedTargets); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCompleted(this); } #endif }
private void CompleteBlockAsync(IList <Exception> exceptions) { Contract.Requires(_decliningPermanently, "We may get here only after we have started to decline permanently."); Contract.Requires(_completionReserved, "We may get here only after we have reserved completion."); Common.ContractAssertMonitorStatus(ValueLock, held: false); // If there is no exceptions list, we offer the message around, and then complete. // If there is an exception list, we complete without offering the message. if (exceptions == null) { // Offer the message to any linked targets and complete the block asynchronously to avoid blocking the caller var taskForOutputProcessing = new Task(state => ((WriteOnceBlock <T>)state).OfferToTargetsAndCompleteBlock(), this, Common.GetCreationOptionsForTask()); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.TaskLaunchedForMessageHandling( this, taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _header.IsValid ? 1 : 0); } #endif // Start the task handling scheduling exceptions Exception exception = Common.StartTaskSafe(taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler); if (exception != null) { CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: true); } } else { // Complete the block asynchronously to avoid blocking the caller Task.Factory.StartNew(state => { Tuple <WriteOnceBlock <T>, IList <Exception> > blockAndList = (Tuple <WriteOnceBlock <T>, IList <Exception> >)state; blockAndList.Item1.CompleteBlock(blockAndList.Item2); }, Tuple.Create(this, exceptions), CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); } }
/// <summary>Initializes the <see cref="ActionBlock{T}"/> with the specified delegate and options.</summary> /// <param name="action">The action to invoke with each data element received.</param> /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="ActionBlock{T}"/>.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="action"/> is null (Nothing in Visual Basic).</exception> /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception> private ActionBlock(Delegate action, ExecutionDataflowBlockOptions dataflowBlockOptions) { // Validate arguments if (action == null) { throw new ArgumentNullException("action"); } if (dataflowBlockOptions == null) { throw new ArgumentNullException("dataflowBlockOptions"); } Contract.Ensures((_spscTarget != null) ^ (_defaultTarget != null), "One and only one of the two targets must be non-null after construction"); Contract.EndContractBlock(); // Ensure we have options that can't be changed by the caller dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // Based on the mode, initialize the target. If the user specifies SingleProducerConstrained, // we'll try to employ an optimized mode under a limited set of circumstances. var syncAction = action as Action <TInput>; if (syncAction != null && dataflowBlockOptions.SingleProducerConstrained && dataflowBlockOptions.MaxDegreeOfParallelism == 1 && !dataflowBlockOptions.CancellationToken.CanBeCanceled && dataflowBlockOptions.BoundedCapacity == DataflowBlockOptions.Unbounded) { // Initialize the SPSC fast target to handle the bulk of the processing. // The SpscTargetCore is only supported when BoundedCapacity, CancellationToken, // and MaxDOP are all their default values. It's also only supported for sync // delegates and not for async delegates. _spscTarget = new SpscTargetCore <TInput>(this, syncAction, dataflowBlockOptions); } else { // Initialize the TargetCore which handles the bulk of the processing. // The default target core can handle all options and delegate flavors. if (syncAction != null) // sync { _defaultTarget = new TargetCore <TInput>(this, messageWithId => ProcessMessage(syncAction, messageWithId), null, dataflowBlockOptions, TargetCoreOptions.RepresentsBlockCompletion); } else // async { var asyncAction = action as Func <TInput, Task>; Debug.Assert(asyncAction != null, "action is of incorrect delegate type"); _defaultTarget = new TargetCore <TInput>(this, messageWithId => ProcessMessageWithTask(asyncAction, messageWithId), null, dataflowBlockOptions, TargetCoreOptions.RepresentsBlockCompletion | TargetCoreOptions.UsesAsyncCompletion); } // Handle async cancellation requests by declining on the target Common.WireCancellationToComplete( dataflowBlockOptions.CancellationToken, Completion, state => ((TargetCore <TInput>)state).Complete(exception: null, dropPendingMessages: true), _defaultTarget); } #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif }
/// <summary>Initializes this <see cref="BatchedJoinBlock{T1,T2}"/> with the specified configuration.</summary> /// <param name="batchSize">The number of items to group into a batch.</param> /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BatchedJoinBlock{T1,T2}"/>.</param> /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception> /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception> public BatchedJoinBlock(int batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) { // Validate arguments if (batchSize < 1) { throw new ArgumentOutOfRangeException(nameof(batchSize), SR.ArgumentOutOfRange_GenericPositive); } if (dataflowBlockOptions == null) { throw new ArgumentNullException(nameof(dataflowBlockOptions)); } if (!dataflowBlockOptions.Greedy) { throw new ArgumentException(SR.Argument_NonGreedyNotSupported, nameof(dataflowBlockOptions)); } if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) { throw new ArgumentException(SR.Argument_BoundedCapacityNotSupported, nameof(dataflowBlockOptions)); } // Store arguments _batchSize = batchSize; dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // Configure the source _source = new SourceCore <Tuple <IList <T1>, IList <T2> > >( this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock <T1, T2>)owningSource).CompleteEachTarget()); // The action to run when a batch should be created. This is typically called // when we have a full batch, but it will also be called when we're done receiving // messages, and thus when there may be a few stragglers we need to make a batch out of. Action createBatchAction = () => { if (_target1.Count > 0 || _target2.Count > 0) { _source.AddMessage(Tuple.Create(_target1.GetAndEmptyMessages(), _target2.GetAndEmptyMessages())); } }; // Configure the targets _sharedResources = new BatchedJoinBlockTargetSharedResources( batchSize, dataflowBlockOptions, createBatchAction, () => { createBatchAction(); _source.Complete(); }, _source.AddException, Complete); _target1 = new BatchedJoinBlockTarget <T1>(_sharedResources); _target2 = new BatchedJoinBlockTarget <T2>(_sharedResources); // 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 = ((BatchedJoinBlock <T1, T2>)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, _source.Completion, state => ((BatchedJoinBlock <T1, T2>)state !).CompleteEachTarget(), this); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif }
/// <summary>Initializes the <see cref="TransformManyBlock{TInput,TOutput}"/> with the specified function and <see cref="ExecutionDataflowBlockOptions"/>.</summary> /// <param name="transformSync">The synchronous function to invoke with each data element received.</param> /// <param name="transformAsync">The asynchronous function to invoke with each data element received.</param> /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="TransformManyBlock{TInput,TOutput}"/>.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="transformSync"/> and <paramref name="transformAsync"/> are both null (Nothing in Visual Basic).</exception> /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception> private TransformManyBlock(Func <TInput, IEnumerable <TOutput> > transformSync, Func <TInput, Task <IEnumerable <TOutput> > > transformAsync, ExecutionDataflowBlockOptions dataflowBlockOptions) { // Validate arguments. It's ok for the filterFunction to be null, but not the other parameters. if (transformSync == null && transformAsync == null) { throw new ArgumentNullException("transform"); } if (dataflowBlockOptions == null) { throw new ArgumentNullException(nameof(dataflowBlockOptions)); } Contract.Requires(transformSync == null ^ transformAsync == null, "Exactly one of transformSync and transformAsync must be null."); Contract.EndContractBlock(); // 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 if (transformSync != null) // sync { // If an enumerable function was provided, we can use synchronous completion, meaning // that the target will consider a message fully processed as soon as the // delegate returns. _target = new TargetCore <TInput>(this, messageWithId => ProcessMessage(transformSync, messageWithId), _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.None); } else // async { Debug.Assert(transformAsync != null, "Incorrect delegate type."); // If a task-based function was provided, we need to use asynchronous completion, meaning // that the target won't consider a message completed until the task // returned from that delegate has completed. _target = new TargetCore <TInput>(this, messageWithId => ProcessMessageWithTask(transformAsync, messageWithId), _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.UsesAsyncCompletion); } // 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); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif }
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); } }