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