// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // BatchedJoinBlock.cs // // // A propagator block that groups individual messages of multiple types // into tuples of arrays of those messages. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Threading.Tasks.Dataflow.Internal; namespace System.Threading.Tasks.Dataflow { /// /// Provides a dataflow block that batches a specified number of inputs of potentially differing types /// provided to one or more of its targets. /// /// Specifies the type of data accepted by the block's first target. /// Specifies the type of data accepted by the block's second target. [DebuggerDisplay("{DebuggerDisplayContent,nq}")] [DebuggerTypeProxy(typeof(BatchedJoinBlock<,>.DebugView))] public sealed class BatchedJoinBlock : IReceivableSourceBlock, IList>>, IDebuggerDisplay { /// The size of the batches generated by this BatchedJoin. private readonly int _batchSize; /// State shared among the targets. private readonly BatchedJoinBlockTargetSharedResources _sharedResources; /// The target providing inputs of type T1. private readonly BatchedJoinBlockTarget _target1; /// The target providing inputs of type T2. private readonly BatchedJoinBlockTarget _target2; /// The source side. private readonly SourceCore, IList>> _source; /// Initializes this with the specified configuration. /// The number of items to group into a batch. /// The must be positive. public BatchedJoinBlock(Int32 batchSize) : this(batchSize, GroupingDataflowBlockOptions.Default) { } /// Initializes this with the specified configuration. /// The number of items to group into a batch. /// The options with which to configure this . /// The must be positive. /// The is null (Nothing in Visual Basic). public BatchedJoinBlock(Int32 batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) { // Validate arguments if (batchSize < 1) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_GenericPositive); if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); if (!dataflowBlockOptions.Greedy) throw new ArgumentException(SR.Argument_NonGreedyNotSupported, "dataflowBlockOptions"); if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) throw new ArgumentException(SR.Argument_BoundedCapacityNotSupported, "dataflowBlockOptions"); Contract.EndContractBlock(); // Store arguments _batchSize = batchSize; dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // Configure the source _source = new SourceCore, IList>>( this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock)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(_sharedResources); _target2 = new BatchedJoinBlockTarget(_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)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)state).CompleteEachTarget(), this); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif } /// Gets the size of the batches generated by this . public Int32 BatchSize { get { return _batchSize; } } /// Gets a target that may be used to offer messages of the first type. public ITargetBlock Target1 { get { return _target1; } } /// Gets a target that may be used to offer messages of the second type. public ITargetBlock Target2 { get { return _target2; } } /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] public IDisposable LinkTo(ITargetBlock, IList>> target, DataflowLinkOptions linkOptions) { return _source.LinkTo(target, linkOptions); } /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] public Boolean TryReceive(Predicate, IList>> filter, out Tuple, IList> item) { return _source.TryReceive(filter, out item); } /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] public bool TryReceiveAll(out IList, IList>> items) { return _source.TryReceiveAll(out items); } /// public int OutputCount { get { return _source.OutputCount; } } /// public Task Completion { get { return _source.Completion; } } /// public void Complete() { Debug.Assert(_target1 != null, "_target1 not initialized"); Debug.Assert(_target2 != null, "_target2 not initialized"); _target1.Complete(); _target2.Complete(); } /// void IDataflowBlock.Fault(Exception exception) { if (exception == null) throw new ArgumentNullException("exception"); Contract.EndContractBlock(); Debug.Assert(_sharedResources != null, "_sharedResources not initialized"); Debug.Assert(_sharedResources._incomingLock != null, "_sharedResources._incomingLock not initialized"); Debug.Assert(_source != null, "_source not initialized"); lock (_sharedResources._incomingLock) { if (!_sharedResources._decliningPermanently) _source.AddException(exception); } Complete(); } /// Tuple, IList> ISourceBlock, IList>>.ConsumeMessage( DataflowMessageHeader messageHeader, ITargetBlock, IList>> target, out Boolean messageConsumed) { return _source.ConsumeMessage(messageHeader, target, out messageConsumed); } /// bool ISourceBlock, IList>>.ReserveMessage( DataflowMessageHeader messageHeader, ITargetBlock, IList>> target) { return _source.ReserveMessage(messageHeader, target); } /// void ISourceBlock, IList>>.ReleaseReservation( DataflowMessageHeader messageHeader, ITargetBlock, IList>> target) { _source.ReleaseReservation(messageHeader, target); } /// /// Invokes Complete on each target /// private void CompleteEachTarget() { _target1.Complete(); _target2.Complete(); } /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } /// public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } /// The data to display in the debugger display attribute. [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] private object DebuggerDisplayContent { get { return string.Format("{0}, BatchSize={1}, OutputCount={2}", Common.GetNameForDebugger(this, _source.DataflowBlockOptions), BatchSize, OutputCountForDebugger); } } /// Gets the data to display in the debugger display attribute for this instance. object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } /// Provides a debugger type proxy for the Transform. private sealed class DebugView { /// The block being viewed. private readonly BatchedJoinBlock _batchedJoinBlock; /// The source half of the block being viewed. private readonly SourceCore, IList>>.DebuggingInformation _sourceDebuggingInformation; /// Initializes the debug view. /// The batched join being viewed. public DebugView(BatchedJoinBlock batchedJoinBlock) { Contract.Requires(batchedJoinBlock != null, "Need a block with which to construct the debug view."); _batchedJoinBlock = batchedJoinBlock; _sourceDebuggingInformation = batchedJoinBlock._source.GetDebuggingInformation(); } /// Gets the messages waiting to be received. public IEnumerable, IList>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } /// Gets the number of batches created. public long BatchesCreated { get { return _batchedJoinBlock._sharedResources._batchesCreated; } } /// Gets the number of items remaining to form a batch. public int RemainingItemsForBatch { get { return _batchedJoinBlock._sharedResources._remainingItemsInBatch; } } /// Gets the size of the batches generated by this BatchedJoin. public Int32 BatchSize { get { return _batchedJoinBlock._batchSize; } } /// Gets the first target. public ITargetBlock Target1 { get { return _batchedJoinBlock._target1; } } /// Gets the second target. public ITargetBlock Target2 { get { return _batchedJoinBlock._target2; } } /// Gets the task being used for output processing. public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } /// Gets the DataflowBlockOptions used to configure this block. public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } } /// Gets whether the block is completed. public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } /// Gets the block's Id. public int Id { get { return Common.GetBlockId(_batchedJoinBlock); } } /// Gets the set of all targets linked from this block. public TargetRegistry, IList>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } /// Gets the target that holds a reservation on the next message, if any. public ITargetBlock, IList>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } } } /// /// Provides a dataflow block that batches a specified number of inputs of potentially differing types /// provided to one or more of its targets. /// /// Specifies the type of data accepted by the block's first target. /// Specifies the type of data accepted by the block's second target. /// Specifies the type of data accepted by the block's third target. [DebuggerDisplay("{DebuggerDisplayContent,nq}")] [DebuggerTypeProxy(typeof(BatchedJoinBlock<,,>.DebugView))] [SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")] public sealed class BatchedJoinBlock : IReceivableSourceBlock, IList, IList>>, IDebuggerDisplay { /// The size of the batches generated by this BatchedJoin. private readonly int _batchSize; /// State shared among the targets. private readonly BatchedJoinBlockTargetSharedResources _sharedResources; /// The target providing inputs of type T1. private readonly BatchedJoinBlockTarget _target1; /// The target providing inputs of type T2. private readonly BatchedJoinBlockTarget _target2; /// The target providing inputs of type T3. private readonly BatchedJoinBlockTarget _target3; /// The source side. private readonly SourceCore, IList, IList>> _source; /// Initializes this with the specified configuration. /// The number of items to group into a batch. /// The must be positive. public BatchedJoinBlock(Int32 batchSize) : this(batchSize, GroupingDataflowBlockOptions.Default) { } /// Initializes this with the specified configuration. /// The number of items to group into a batch. /// The options with which to configure this . /// The must be positive. /// The is null (Nothing in Visual Basic). public BatchedJoinBlock(Int32 batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) { // Validate arguments if (batchSize < 1) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_GenericPositive); if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); if (!dataflowBlockOptions.Greedy || dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) { throw new ArgumentException(SR.Argument_NonGreedyNotSupported, "dataflowBlockOptions"); } Contract.EndContractBlock(); // Store arguments _batchSize = batchSize; dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // Configure the source _source = new SourceCore, IList, IList>>( this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock)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 || _target3.Count > 0) { _source.AddMessage(Tuple.Create(_target1.GetAndEmptyMessages(), _target2.GetAndEmptyMessages(), _target3.GetAndEmptyMessages())); } }; // Configure the targets _sharedResources = new BatchedJoinBlockTargetSharedResources( batchSize, dataflowBlockOptions, createBatchAction, () => { createBatchAction(); _source.Complete(); }, _source.AddException, Complete); _target1 = new BatchedJoinBlockTarget(_sharedResources); _target2 = new BatchedJoinBlockTarget(_sharedResources); _target3 = new BatchedJoinBlockTarget(_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)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)state).CompleteEachTarget(), this); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif } /// Gets the size of the batches generated by this . public Int32 BatchSize { get { return _batchSize; } } /// Gets a target that may be used to offer messages of the first type. public ITargetBlock Target1 { get { return _target1; } } /// Gets a target that may be used to offer messages of the second type. public ITargetBlock Target2 { get { return _target2; } } /// Gets a target that may be used to offer messages of the third type. public ITargetBlock Target3 { get { return _target3; } } /// public IDisposable LinkTo(ITargetBlock, IList, IList>> target, DataflowLinkOptions linkOptions) { return _source.LinkTo(target, linkOptions); } /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] public Boolean TryReceive(Predicate, IList, IList>> filter, out Tuple, IList, IList> item) { return _source.TryReceive(filter, out item); } /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] public bool TryReceiveAll(out IList, IList, IList>> items) { return _source.TryReceiveAll(out items); } /// public int OutputCount { get { return _source.OutputCount; } } /// public Task Completion { get { return _source.Completion; } } /// public void Complete() { Debug.Assert(_target1 != null, "_target1 not initialized"); Debug.Assert(_target2 != null, "_target2 not initialized"); Debug.Assert(_target3 != null, "_target3 not initialized"); _target1.Complete(); _target2.Complete(); _target3.Complete(); } /// void IDataflowBlock.Fault(Exception exception) { if (exception == null) throw new ArgumentNullException("exception"); Contract.EndContractBlock(); Debug.Assert(_sharedResources != null, "_sharedResources not initialized"); Debug.Assert(_sharedResources._incomingLock != null, "_sharedResources._incomingLock not initialized"); Debug.Assert(_source != null, "_source not initialized"); lock (_sharedResources._incomingLock) { if (!_sharedResources._decliningPermanently) _source.AddException(exception); } Complete(); } /// Tuple, IList, IList> ISourceBlock, IList, IList>>.ConsumeMessage( DataflowMessageHeader messageHeader, ITargetBlock, IList, IList>> target, out Boolean messageConsumed) { return _source.ConsumeMessage(messageHeader, target, out messageConsumed); } /// bool ISourceBlock, IList, IList>>.ReserveMessage( DataflowMessageHeader messageHeader, ITargetBlock, IList, IList>> target) { return _source.ReserveMessage(messageHeader, target); } /// void ISourceBlock, IList, IList>>.ReleaseReservation( DataflowMessageHeader messageHeader, ITargetBlock, IList, IList>> target) { _source.ReleaseReservation(messageHeader, target); } /// /// Invokes Complete on each target /// private void CompleteEachTarget() { _target1.Complete(); _target2.Complete(); _target3.Complete(); } /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } /// public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } /// The data to display in the debugger display attribute. [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] private object DebuggerDisplayContent { get { return string.Format("{0}, BatchSize={1}, OutputCount={2}", Common.GetNameForDebugger(this, _source.DataflowBlockOptions), BatchSize, OutputCountForDebugger); } } /// Gets the data to display in the debugger display attribute for this instance. object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } /// Provides a debugger type proxy for the Transform. private sealed class DebugView { /// The block being viewed. private readonly BatchedJoinBlock _batchedJoinBlock; /// The source half of the block being viewed. private readonly SourceCore, IList, IList>>.DebuggingInformation _sourceDebuggingInformation; /// Initializes the debug view. /// The batched join being viewed. public DebugView(BatchedJoinBlock batchedJoinBlock) { Contract.Requires(batchedJoinBlock != null, "Need a block with which to construct the debug view."); _sourceDebuggingInformation = batchedJoinBlock._source.GetDebuggingInformation(); _batchedJoinBlock = batchedJoinBlock; } /// Gets the messages waiting to be received. public IEnumerable, IList, IList>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } /// Gets the number of batches created. public long BatchesCreated { get { return _batchedJoinBlock._sharedResources._batchesCreated; } } /// Gets the number of items remaining to form a batch. public int RemainingItemsForBatch { get { return _batchedJoinBlock._sharedResources._remainingItemsInBatch; } } /// Gets the size of the batches generated by this BatchedJoin. public Int32 BatchSize { get { return _batchedJoinBlock._batchSize; } } /// Gets the first target. public ITargetBlock Target1 { get { return _batchedJoinBlock._target1; } } /// Gets the second target. public ITargetBlock Target2 { get { return _batchedJoinBlock._target2; } } /// Gets the second target. public ITargetBlock Target3 { get { return _batchedJoinBlock._target3; } } /// Gets the task being used for output processing. public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } /// Gets the DataflowBlockOptions used to configure this block. public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } } /// Gets whether the block is completed. public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } /// Gets the block's Id. public int Id { get { return Common.GetBlockId(_batchedJoinBlock); } } /// Gets the set of all targets linked from this block. public TargetRegistry, IList, IList>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } /// Gets the target that holds a reservation on the next message, if any. public ITargetBlock, IList, IList>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } } } } namespace System.Threading.Tasks.Dataflow.Internal { /// Provides the target used in a BatchedJoin. /// Specifies the type of data accepted by this target. [DebuggerDisplay("{DebuggerDisplayContent,nq}")] [DebuggerTypeProxy(typeof(BatchedJoinBlockTarget<>.DebugView))] internal sealed class BatchedJoinBlockTarget : ITargetBlock, IDebuggerDisplay { /// The shared resources used by all targets associated with the same batched join instance. private readonly BatchedJoinBlockTargetSharedResources _sharedResources; /// Whether this target is declining future messages. private bool _decliningPermanently; /// Input messages for the next batch. private IList _messages = new List(); /// Initializes the target. /// The shared resources used by all targets associated with this batched join. internal BatchedJoinBlockTarget(BatchedJoinBlockTargetSharedResources sharedResources) { Contract.Requires(sharedResources != null, "Targets require a shared resources through which to communicate."); // Store the shared resources, and register with it to let it know there's // another target. This is done in a non-thread-safe manner and must be done // during construction of the batched join instance. _sharedResources = sharedResources; sharedResources._remainingAliveTargets++; } /// Gets the number of messages buffered in this target. internal int Count { get { return _messages.Count; } } /// Gets the messages buffered by this target and then empties the collection. /// The messages from the target. internal IList GetAndEmptyMessages() { Common.ContractAssertMonitorStatus(_sharedResources._incomingLock, held: true); IList toReturn = _messages; _messages = new List(); return toReturn; } /// public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) { // Validate arguments if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); Contract.EndContractBlock(); lock (_sharedResources._incomingLock) { // If we've already stopped accepting messages, decline permanently if (_decliningPermanently || _sharedResources._decliningPermanently) return DataflowMessageStatus.DecliningPermanently; // Consume the message from the source if necessary, and store the message if (consumeToAccept) { Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); bool consumed; messageValue = source.ConsumeMessage(messageHeader, this, out consumed); if (!consumed) return DataflowMessageStatus.NotAvailable; } _messages.Add(messageValue); // If this message makes a batch, notify the shared resources that a batch has been completed if (--_sharedResources._remainingItemsInBatch == 0) _sharedResources._batchSizeReachedAction(); return DataflowMessageStatus.Accepted; } } /// public void Complete() { lock (_sharedResources._incomingLock) { // If this is the first time Complete is being called, // note that there's now one fewer targets receiving messages for the batched join. if (!_decliningPermanently) { _decliningPermanently = true; if (--_sharedResources._remainingAliveTargets == 0) _sharedResources._allTargetsDecliningPermanentlyAction(); } } } /// void IDataflowBlock.Fault(Exception exception) { if (exception == null) throw new ArgumentNullException("exception"); Contract.EndContractBlock(); lock (_sharedResources._incomingLock) { if (!_decliningPermanently && !_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception); } _sharedResources._completionAction(); } /// Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } } /// The data to display in the debugger display attribute. [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] private object DebuggerDisplayContent { get { return string.Format("{0} InputCount={1}", Common.GetNameForDebugger(this), _messages.Count); } } /// Gets the data to display in the debugger display attribute for this instance. object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } /// Provides a debugger type proxy for the Transform. private sealed class DebugView { /// The batched join block target being viewed. private readonly BatchedJoinBlockTarget _batchedJoinBlockTarget; /// Initializes the debug view. /// The batched join target being viewed. public DebugView(BatchedJoinBlockTarget batchedJoinBlockTarget) { Contract.Requires(batchedJoinBlockTarget != null, "Need a block with which to construct the debug view."); _batchedJoinBlockTarget = batchedJoinBlockTarget; } /// Gets the messages waiting to be processed. public IEnumerable InputQueue { get { return _batchedJoinBlockTarget._messages; } } /// Gets whether the block is declining further messages. public bool IsDecliningPermanently { get { return _batchedJoinBlockTarget._decliningPermanently || _batchedJoinBlockTarget._sharedResources._decliningPermanently; } } } } /// Provides a container for resources shared across all targets used by the same BatchedJoinBlock instance. internal sealed class BatchedJoinBlockTargetSharedResources { /// Initializes the shared resources. /// The size of a batch to create. /// The options used to configure the shared resources. Assumed to be immutable. /// The action to invoke when a batch is completed. /// The action to invoke when no more targets are accepting input. /// The action to invoke when an exception needs to be logged. /// The action to invoke when completing, typically invoked due to a call to Fault. internal BatchedJoinBlockTargetSharedResources( int batchSize, GroupingDataflowBlockOptions dataflowBlockOptions, Action batchSizeReachedAction, Action allTargetsDecliningAction, Action exceptionAction, Action completionAction) { Debug.Assert(batchSize >= 1, "A positive batch size is required."); Debug.Assert(batchSizeReachedAction != null, "Need an action to invoke for each batch."); Debug.Assert(allTargetsDecliningAction != null, "Need an action to invoke when all targets have declined."); _incomingLock = new object(); _batchSize = batchSize; // _remainingAliveTargets will be incremented when targets are added. // They must be added during construction of the BatchedJoin<...>. _remainingAliveTargets = 0; _remainingItemsInBatch = batchSize; // Configure what to do when batches are completed and/or all targets start declining _allTargetsDecliningPermanentlyAction = () => { // Invoke the caller's action allTargetsDecliningAction(); // Don't accept any more messages. We should already // be doing this anyway through each individual target's declining flag, // so setting it to true is just a precaution and is also helpful // when onceOnly is true. _decliningPermanently = true; }; _batchSizeReachedAction = () => { // Invoke the caller's action batchSizeReachedAction(); _batchesCreated++; // If this batched join is meant to be used for only a single // batch, invoke the completion logic. if (_batchesCreated >= dataflowBlockOptions.ActualMaxNumberOfGroups) _allTargetsDecliningPermanentlyAction(); // Otherwise, get ready for the next batch. else _remainingItemsInBatch = _batchSize; }; _exceptionAction = exceptionAction; _completionAction = completionAction; } /// /// A lock used to synchronize all incoming messages on all targets. It protects all of the rest /// of the shared Resources's state and will be held while invoking the delegates. /// internal readonly object _incomingLock; /// The size of the batches to generate. internal readonly int _batchSize; /// The action to invoke when enough elements have been accumulated to make a batch. internal readonly Action _batchSizeReachedAction; /// The action to invoke when all targets are declining further messages. internal readonly Action _allTargetsDecliningPermanentlyAction; /// The action to invoke when an exception has to be logged. internal readonly Action _exceptionAction; /// The action to invoke when the owning block has to be completed. internal readonly Action _completionAction; /// The number of items remaining to form a batch. internal int _remainingItemsInBatch; /// The number of targets still alive (i.e. not declining all further messages). internal int _remainingAliveTargets; /// Whether all targets should decline all further messages. internal bool _decliningPermanently; /// The number of batches created. internal long _batchesCreated; } }