/// <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 }