You've already forked linux-packaging-mono
Imported Upstream version 4.3.2.467
Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
This commit is contained in:
@ -0,0 +1,383 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// ActionBlock.cs
|
||||
//
|
||||
//
|
||||
// A target block that executes an action for each message.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Dataflow.Internal;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow
|
||||
{
|
||||
/// <summary>Provides a dataflow block that invokes a provided <see cref="System.Action{T}"/> delegate for every data element received.</summary>
|
||||
/// <typeparam name="TInput">Specifies the type of data operated on by this <see cref="ActionBlock{T}"/>.</typeparam>
|
||||
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
|
||||
[DebuggerTypeProxy(typeof(ActionBlock<>.DebugView))]
|
||||
public sealed class ActionBlock<TInput> : ITargetBlock<TInput>, IDebuggerDisplay
|
||||
{
|
||||
/// <summary>The core implementation of this message block when in default mode.</summary>
|
||||
private readonly TargetCore<TInput> _defaultTarget;
|
||||
/// <summary>The core implementation of this message block when in SPSC mode.</summary>
|
||||
private readonly SpscTargetCore<TInput> _spscTarget;
|
||||
|
||||
/// <summary>Initializes the <see cref="ActionBlock{T}"/> with the specified <see cref="System.Action{T}"/>.</summary>
|
||||
/// <param name="action">The action to invoke with each data element received.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="action"/> is null (Nothing in Visual Basic).</exception>
|
||||
public ActionBlock(Action<TInput> action) :
|
||||
this((Delegate)action, ExecutionDataflowBlockOptions.Default)
|
||||
{ }
|
||||
|
||||
/// <summary>Initializes the <see cref="ActionBlock{T}"/> with the specified <see cref="System.Action{T}"/> and <see cref="ExecutionDataflowBlockOptions"/>.</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>
|
||||
public ActionBlock(Action<TInput> action, ExecutionDataflowBlockOptions dataflowBlockOptions) :
|
||||
this((Delegate)action, dataflowBlockOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>Initializes the <see cref="ActionBlock{T}"/> with the specified <see cref="System.Func{T,Task}"/>.</summary>
|
||||
/// <param name="action">The action to invoke with each data element received.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="action"/> is null (Nothing in Visual Basic).</exception>
|
||||
public ActionBlock(Func<TInput, Task> action) :
|
||||
this((Delegate)action, ExecutionDataflowBlockOptions.Default)
|
||||
{ }
|
||||
|
||||
/// <summary>Initializes the <see cref="ActionBlock{T}"/> with the specified <see cref="System.Func{T,Task}"/> and <see cref="ExecutionDataflowBlockOptions"/>.</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>
|
||||
public ActionBlock(Func<TInput, Task> action, ExecutionDataflowBlockOptions dataflowBlockOptions) :
|
||||
this((Delegate)action, dataflowBlockOptions)
|
||||
{ }
|
||||
|
||||
/// <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>Processes the message with a user-provided action.</summary>
|
||||
/// <param name="action">The action to use to process the message.</param>
|
||||
/// <param name="messageWithId">The message to be processed.</param>
|
||||
private void ProcessMessage(Action<TInput> action, KeyValuePair<TInput, long> messageWithId)
|
||||
{
|
||||
try
|
||||
{
|
||||
action(messageWithId.Key);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
// If this exception represents cancellation, swallow it rather than shutting down the block.
|
||||
if (!Common.IsCooperativeCancellation(exc)) throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// We're done synchronously processing an element, so reduce the bounding count
|
||||
// that was incrementing when this element was enqueued.
|
||||
if (_defaultTarget.IsBounded) _defaultTarget.ChangeBoundingCount(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Processes the message with a user-provided action that returns a task.</summary>
|
||||
/// <param name="action">The action to use to process the message.</param>
|
||||
/// <param name="messageWithId">The message to be processed.</param>
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
private void ProcessMessageWithTask(Func<TInput, Task> action, KeyValuePair<TInput, long> messageWithId)
|
||||
{
|
||||
Contract.Requires(action != null, "action needed for processing");
|
||||
|
||||
// Run the action to get the task that represents the operation's completion
|
||||
Task task = null;
|
||||
Exception caughtException = null;
|
||||
try
|
||||
{
|
||||
task = action(messageWithId.Key);
|
||||
}
|
||||
catch (Exception exc) { caughtException = exc; }
|
||||
|
||||
// If no task is available, we're done.
|
||||
if (task == null)
|
||||
{
|
||||
// If we didn't get a task because an exception occurred,
|
||||
// store it (if the exception was cancellation, just ignore it).
|
||||
if (caughtException != null && !Common.IsCooperativeCancellation(caughtException))
|
||||
{
|
||||
Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key);
|
||||
_defaultTarget.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false);
|
||||
}
|
||||
|
||||
// Signal that we're done this async operation.
|
||||
_defaultTarget.SignalOneAsyncMessageCompleted(boundingCountChange: -1);
|
||||
return;
|
||||
}
|
||||
else if (task.IsCompleted)
|
||||
{
|
||||
AsyncCompleteProcessMessageWithTask(task);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, join with the asynchronous operation when it completes.
|
||||
task.ContinueWith((completed, state) =>
|
||||
{
|
||||
((ActionBlock<TInput>)state).AsyncCompleteProcessMessageWithTask(completed);
|
||||
}, this, CancellationToken.None, Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Completes the processing of an asynchronous message.</summary>
|
||||
/// <param name="completed">The completed task.</param>
|
||||
private void AsyncCompleteProcessMessageWithTask(Task completed)
|
||||
{
|
||||
Contract.Requires(completed != null, "Need completed task for processing");
|
||||
Contract.Requires(completed.IsCompleted, "The task to be processed must be completed by now.");
|
||||
|
||||
// If the task faulted, store its errors. We must add the exception before declining
|
||||
// and signaling completion, as the exception is part of the operation, and the completion conditions
|
||||
// depend on this.
|
||||
if (completed.IsFaulted)
|
||||
{
|
||||
_defaultTarget.Complete(completed.Exception, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: true);
|
||||
}
|
||||
|
||||
// Regardless of faults, note that we're done processing. There are
|
||||
// no outputs to keep track of for action block, so we always decrement
|
||||
// the bounding count here (the callee will handle checking whether
|
||||
// we're actually in a bounded mode).
|
||||
_defaultTarget.SignalOneAsyncMessageCompleted(boundingCountChange: -1);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
|
||||
public void Complete()
|
||||
{
|
||||
if (_defaultTarget != null)
|
||||
{
|
||||
_defaultTarget.Complete(exception: null, dropPendingMessages: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_spscTarget.Complete(exception: null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
|
||||
void IDataflowBlock.Fault(Exception exception)
|
||||
{
|
||||
if (exception == null) throw new ArgumentNullException("exception");
|
||||
Contract.EndContractBlock();
|
||||
|
||||
if (_defaultTarget != null)
|
||||
{
|
||||
_defaultTarget.Complete(exception, dropPendingMessages: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_spscTarget.Complete(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
|
||||
public Task Completion
|
||||
{
|
||||
get { return _defaultTarget != null ? _defaultTarget.Completion : _spscTarget.Completion; }
|
||||
}
|
||||
|
||||
/// <summary>Posts an item to the <see cref="T:System.Threading.Tasks.Dataflow.ITargetBlock`1"/>.</summary>
|
||||
/// <param name="item">The item being offered to the target.</param>
|
||||
/// <returns>true if the item was accepted by the target block; otherwise, false.</returns>
|
||||
/// <remarks>
|
||||
/// This method will return once the target block has decided to accept or decline the item,
|
||||
/// but unless otherwise dictated by special semantics of the target block, it does not wait
|
||||
/// for the item to actually be processed (for example, <see cref="T:System.Threading.Tasks.Dataflow.ActionBlock`1"/>
|
||||
/// will return from Post as soon as it has stored the posted item into its input queue). From the perspective
|
||||
/// of the block's processing, Post is asynchronous. For target blocks that support postponing offered messages,
|
||||
/// or for blocks that may do more processing in their Post implementation, consider using
|
||||
/// <see cref="T:System.Threading.Tasks.Dataflow.DataflowBlock.SendAsync">SendAsync</see>,
|
||||
/// which will return immediately and will enable the target to postpone the posted message and later consume it
|
||||
/// after SendAsync returns.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Post(TInput item)
|
||||
{
|
||||
// Even though this method is available with the exact same functionality as an extension method
|
||||
// on ITargetBlock, using that extension method goes through an interface call on ITargetBlock,
|
||||
// which for very high-throughput scenarios shows up as noticeable overhead on certain architectures.
|
||||
// We can eliminate that call for direct ActionBlock usage by providing the same method as an instance method.
|
||||
|
||||
return _defaultTarget != null ?
|
||||
_defaultTarget.OfferMessage(Common.SingleMessageHeader, item, null, false) == DataflowMessageStatus.Accepted :
|
||||
_spscTarget.Post(item);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
|
||||
DataflowMessageStatus ITargetBlock<TInput>.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, Boolean consumeToAccept)
|
||||
{
|
||||
return _defaultTarget != null ?
|
||||
_defaultTarget.OfferMessage(messageHeader, messageValue, source, consumeToAccept) :
|
||||
_spscTarget.OfferMessage(messageHeader, messageValue, source, consumeToAccept);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="InputCount"]/*' />
|
||||
public int InputCount
|
||||
{
|
||||
get { return _defaultTarget != null ? _defaultTarget.InputCount : _spscTarget.InputCount; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of messages waiting to be processed. This must only be used from the debugger.</summary>
|
||||
private int InputCountForDebugger
|
||||
{
|
||||
get { return _defaultTarget != null ? _defaultTarget.GetDebuggingInformation().InputCount : _spscTarget.InputCount; }
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="ToString"]/*' />
|
||||
public override string ToString()
|
||||
{
|
||||
return Common.GetNameForDebugger(this, _defaultTarget != null ? _defaultTarget.DataflowBlockOptions : _spscTarget.DataflowBlockOptions);
|
||||
}
|
||||
|
||||
/// <summary>The data to display in the debugger display attribute.</summary>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
|
||||
private object DebuggerDisplayContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}, InputCount={1}",
|
||||
Common.GetNameForDebugger(this, _defaultTarget != null ? _defaultTarget.DataflowBlockOptions : _spscTarget.DataflowBlockOptions),
|
||||
InputCountForDebugger);
|
||||
}
|
||||
}
|
||||
/// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
|
||||
object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
|
||||
|
||||
/// <summary>Provides a debugger type proxy for the Call.</summary>
|
||||
private sealed class DebugView
|
||||
{
|
||||
/// <summary>The action block being viewed.</summary>
|
||||
private readonly ActionBlock<TInput> _actionBlock;
|
||||
/// <summary>The action block's default target being viewed.</summary>
|
||||
private readonly TargetCore<TInput>.DebuggingInformation _defaultDebugInfo;
|
||||
/// <summary>The action block's SPSC target being viewed.</summary>
|
||||
private readonly SpscTargetCore<TInput>.DebuggingInformation _spscDebugInfo;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="actionBlock">The target being debugged.</param>
|
||||
public DebugView(ActionBlock<TInput> actionBlock)
|
||||
{
|
||||
Contract.Requires(actionBlock != null, "Need a block with which to construct the debug view.");
|
||||
_actionBlock = actionBlock;
|
||||
if (_actionBlock._defaultTarget != null)
|
||||
{
|
||||
_defaultDebugInfo = actionBlock._defaultTarget.GetDebuggingInformation();
|
||||
}
|
||||
else
|
||||
{
|
||||
_spscDebugInfo = actionBlock._spscTarget.GetDebuggingInformation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the messages waiting to be processed.</summary>
|
||||
public IEnumerable<TInput> InputQueue
|
||||
{
|
||||
get { return _defaultDebugInfo != null ? _defaultDebugInfo.InputQueue : _spscDebugInfo.InputQueue; }
|
||||
}
|
||||
/// <summary>Gets any postponed messages.</summary>
|
||||
public QueuedMap<ISourceBlock<TInput>, DataflowMessageHeader> PostponedMessages
|
||||
{
|
||||
get { return _defaultDebugInfo != null ? _defaultDebugInfo.PostponedMessages : null; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of outstanding input operations.</summary>
|
||||
public Int32 CurrentDegreeOfParallelism
|
||||
{
|
||||
get { return _defaultDebugInfo != null ? _defaultDebugInfo.CurrentDegreeOfParallelism : _spscDebugInfo.CurrentDegreeOfParallelism; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the ExecutionDataflowBlockOptions used to configure this block.</summary>
|
||||
public ExecutionDataflowBlockOptions DataflowBlockOptions
|
||||
{
|
||||
get { return _defaultDebugInfo != null ? _defaultDebugInfo.DataflowBlockOptions : _spscDebugInfo.DataflowBlockOptions; }
|
||||
}
|
||||
/// <summary>Gets whether the block is declining further messages.</summary>
|
||||
public bool IsDecliningPermanently
|
||||
{
|
||||
get { return _defaultDebugInfo != null ? _defaultDebugInfo.IsDecliningPermanently : _spscDebugInfo.IsDecliningPermanently; }
|
||||
}
|
||||
/// <summary>Gets whether the block is completed.</summary>
|
||||
public bool IsCompleted
|
||||
{
|
||||
get { return _defaultDebugInfo != null ? _defaultDebugInfo.IsCompleted : _spscDebugInfo.IsCompleted; }
|
||||
}
|
||||
/// <summary>Gets the block's Id.</summary>
|
||||
public int Id { get { return Common.GetBlockId(_actionBlock); } }
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,492 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// BufferBlock.cs
|
||||
//
|
||||
//
|
||||
// A propagator block that provides support for unbounded and bounded FIFO buffers.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Security;
|
||||
using System.Threading.Tasks.Dataflow.Internal;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow
|
||||
{
|
||||
/// <summary>Provides a buffer for storing data.</summary>
|
||||
/// <typeparam name="T">Specifies the type of the data buffered by this dataflow block.</typeparam>
|
||||
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
|
||||
[DebuggerTypeProxy(typeof(BufferBlock<>.DebugView))]
|
||||
public sealed class BufferBlock<T> : IPropagatorBlock<T, T>, IReceivableSourceBlock<T>, IDebuggerDisplay
|
||||
{
|
||||
/// <summary>The core logic for the buffer block.</summary>
|
||||
private readonly SourceCore<T> _source;
|
||||
/// <summary>The bounding state for when in bounding mode; null if not bounding.</summary>
|
||||
private readonly BoundingStateWithPostponedAndTask<T> _boundingState;
|
||||
/// <summary>Whether all future messages should be declined on the target.</summary>
|
||||
private bool _targetDecliningPermanently;
|
||||
/// <summary>A task has reserved the right to run the target's completion routine.</summary>
|
||||
private bool _targetCompletionReserved;
|
||||
/// <summary>Gets the lock object used to synchronize incoming requests.</summary>
|
||||
private object IncomingLock { get { return _source; } }
|
||||
|
||||
/// <summary>Initializes the <see cref="BufferBlock{T}"/>.</summary>
|
||||
public BufferBlock() :
|
||||
this(DataflowBlockOptions.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.
|
||||
_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);
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
|
||||
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> 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 (IncomingLock)
|
||||
{
|
||||
// If we've already stopped accepting messages, decline permanently
|
||||
if (_targetDecliningPermanently)
|
||||
{
|
||||
CompleteTargetIfPossible();
|
||||
return DataflowMessageStatus.DecliningPermanently;
|
||||
}
|
||||
|
||||
// We can directly accept the message if:
|
||||
// 1) we are not bounding, OR
|
||||
// 2) we are bounding AND there is room available AND there are no postponed messages AND we are not currently processing.
|
||||
// (If there were any postponed messages, we would need to postpone so that ordering would be maintained.)
|
||||
// (We should also postpone if we are currently processing, because there may be a race between consuming postponed messages and
|
||||
// accepting new ones directly into the queue.)
|
||||
if (_boundingState == null
|
||||
||
|
||||
(_boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count == 0 && _boundingState.TaskForInputProcessing == null))
|
||||
{
|
||||
// Consume the message from the source if necessary
|
||||
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;
|
||||
}
|
||||
|
||||
// Once consumed, pass it to the source
|
||||
_source.AddMessage(messageValue);
|
||||
if (_boundingState != null) _boundingState.CurrentCount++;
|
||||
|
||||
return DataflowMessageStatus.Accepted;
|
||||
}
|
||||
// Otherwise, we try to postpone if a source was provided
|
||||
else if (source != null)
|
||||
{
|
||||
Debug.Assert(_boundingState != null && _boundingState.PostponedMessages != null,
|
||||
"PostponedMessages must have been initialized during construction in bounding mode.");
|
||||
|
||||
_boundingState.PostponedMessages.Push(source, messageHeader);
|
||||
return DataflowMessageStatus.Postponed;
|
||||
}
|
||||
// We can't do anything else about this message
|
||||
return DataflowMessageStatus.Declined;
|
||||
}
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
|
||||
public void Complete() { CompleteCore(exception: null, storeExceptionEvenIfAlreadyCompleting: false); }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
|
||||
void IDataflowBlock.Fault(Exception exception)
|
||||
{
|
||||
if (exception == null) throw new ArgumentNullException("exception");
|
||||
Contract.EndContractBlock();
|
||||
|
||||
CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: false);
|
||||
}
|
||||
|
||||
private void CompleteCore(Exception exception, bool storeExceptionEvenIfAlreadyCompleting, bool revertProcessingState = false)
|
||||
{
|
||||
Contract.Requires(storeExceptionEvenIfAlreadyCompleting || !revertProcessingState,
|
||||
"Indicating dirty processing state may only come with storeExceptionEvenIfAlreadyCompleting==true.");
|
||||
Contract.EndContractBlock();
|
||||
|
||||
lock (IncomingLock)
|
||||
{
|
||||
// Faulting from outside is allowed until we start declining permanently.
|
||||
// Faulting from inside is allowed at any time.
|
||||
if (exception != null && (!_targetDecliningPermanently || storeExceptionEvenIfAlreadyCompleting))
|
||||
{
|
||||
_source.AddException(exception);
|
||||
}
|
||||
|
||||
// Revert the dirty processing state if requested
|
||||
if (revertProcessingState)
|
||||
{
|
||||
Debug.Assert(_boundingState != null && _boundingState.TaskForInputProcessing != null,
|
||||
"The processing state must be dirty when revertProcessingState==true.");
|
||||
_boundingState.TaskForInputProcessing = null;
|
||||
}
|
||||
|
||||
// Trigger completion
|
||||
_targetDecliningPermanently = true;
|
||||
CompleteTargetIfPossible();
|
||||
}
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
|
||||
public IDisposable LinkTo(ITargetBlock<T> target, DataflowLinkOptions linkOptions) { return _source.LinkTo(target, linkOptions); }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
|
||||
public Boolean TryReceive(Predicate<T> filter, out T item) { return _source.TryReceive(filter, out item); }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
|
||||
public Boolean TryReceiveAll(out IList<T> items) { return _source.TryReceiveAll(out items); }
|
||||
|
||||
/// <summary>Gets the number of items currently stored in the buffer.</summary>
|
||||
public Int32 Count { get { return _source.OutputCount; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
|
||||
public Task Completion { get { return _source.Completion; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
|
||||
T ISourceBlock<T>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target, out Boolean messageConsumed)
|
||||
{
|
||||
return _source.ConsumeMessage(messageHeader, target, out messageConsumed);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
|
||||
bool ISourceBlock<T>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
|
||||
{
|
||||
return _source.ReserveMessage(messageHeader, target);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
|
||||
void ISourceBlock<T>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
|
||||
{
|
||||
_source.ReleaseReservation(messageHeader, target);
|
||||
}
|
||||
|
||||
/// <summary>Notifies the block that one or more items was removed from the queue.</summary>
|
||||
/// <param name="numItemsRemoved">The number of items removed.</param>
|
||||
private void OnItemsRemoved(int numItemsRemoved)
|
||||
{
|
||||
Contract.Requires(numItemsRemoved > 0, "A positive number of items to remove is required.");
|
||||
Common.ContractAssertMonitorStatus(IncomingLock, held: false);
|
||||
|
||||
// If we're bounding, we need to know when an item is removed so that we
|
||||
// can update the count that's mirroring the actual count in the source's queue,
|
||||
// and potentially kick off processing to start consuming postponed messages.
|
||||
if (_boundingState != null)
|
||||
{
|
||||
lock (IncomingLock)
|
||||
{
|
||||
// Decrement the count, which mirrors the count in the source half
|
||||
Debug.Assert(_boundingState.CurrentCount - numItemsRemoved >= 0,
|
||||
"It should be impossible to have a negative number of items.");
|
||||
_boundingState.CurrentCount -= numItemsRemoved;
|
||||
|
||||
ConsumeAsyncIfNecessary();
|
||||
CompleteTargetIfPossible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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));
|
||||
|
||||
#if FEATURE_TRACING
|
||||
DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
|
||||
if (etwLog.IsEnabled())
|
||||
{
|
||||
etwLog.TaskLaunchedForMessageHandling(
|
||||
this, _boundingState.TaskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages,
|
||||
_boundingState.PostponedMessages.Count);
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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>Task body used to consume postponed messages.</summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
private void ConsumeMessagesLoopCore()
|
||||
{
|
||||
Contract.Requires(_boundingState != null && _boundingState.TaskForInputProcessing != null,
|
||||
"May only be called in bounded mode and when a task is in flight.");
|
||||
Debug.Assert(_boundingState.TaskForInputProcessing.Id == Task.CurrentId,
|
||||
"This must only be called from the in-flight processing task.");
|
||||
Common.ContractAssertMonitorStatus(IncomingLock, held: false);
|
||||
|
||||
try
|
||||
{
|
||||
int maxMessagesPerTask = _source.DataflowBlockOptions.ActualMaxMessagesPerTask;
|
||||
for (int i = 0;
|
||||
i < maxMessagesPerTask && ConsumeAndStoreOneMessageIfAvailable();
|
||||
i++)
|
||||
;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
// Prevent the creation of new processing tasks
|
||||
CompleteCore(exc, storeExceptionEvenIfAlreadyCompleting: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (IncomingLock)
|
||||
{
|
||||
// We're no longer processing, so null out the processing task
|
||||
_boundingState.TaskForInputProcessing = null;
|
||||
|
||||
// However, we may have given up early because we hit our own configured
|
||||
// processing limits rather than because we ran out of work to do. If that's
|
||||
// the case, make sure we spin up another task to keep going.
|
||||
ConsumeAsyncIfNecessary(isReplacementReplica: true);
|
||||
|
||||
// If, however, we stopped because we ran out of work to do and we
|
||||
// know we'll never get more, then complete.
|
||||
CompleteTargetIfPossible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves one postponed message if there's room and if we can consume a postponed message.
|
||||
/// Stores any consumed message into the source half.
|
||||
/// </summary>
|
||||
/// <returns>true if a message could be consumed and stored; otherwise, false.</returns>
|
||||
/// <remarks>This must only be called from the asynchronous processing loop.</remarks>
|
||||
private bool ConsumeAndStoreOneMessageIfAvailable()
|
||||
{
|
||||
Contract.Requires(_boundingState != null && _boundingState.TaskForInputProcessing != null,
|
||||
"May only be called in bounded mode and when a task is in flight.");
|
||||
Debug.Assert(_boundingState.TaskForInputProcessing.Id == Task.CurrentId,
|
||||
"This must only be called from the in-flight processing task.");
|
||||
Common.ContractAssertMonitorStatus(IncomingLock, held: false);
|
||||
|
||||
// Loop through the postponed messages until we get one.
|
||||
while (true)
|
||||
{
|
||||
// Get the next item to retrieve. If there are no more, bail.
|
||||
KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> sourceAndMessage;
|
||||
lock (IncomingLock)
|
||||
{
|
||||
if (!_boundingState.CountIsLessThanBound) return false;
|
||||
if (!_boundingState.PostponedMessages.TryPop(out sourceAndMessage)) return false;
|
||||
|
||||
// Optimistically assume we're going to get the item. This avoids taking the lock
|
||||
// again if we're right. If we're wrong, we decrement it later under lock.
|
||||
_boundingState.CurrentCount++;
|
||||
}
|
||||
|
||||
// Consume the item
|
||||
bool consumed = false;
|
||||
try
|
||||
{
|
||||
T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value, this, out consumed);
|
||||
if (consumed)
|
||||
{
|
||||
_source.AddMessage(consumedValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// We didn't get the item, so decrement the count to counteract our optimistic assumption.
|
||||
if (!consumed)
|
||||
{
|
||||
lock (IncomingLock) _boundingState.CurrentCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Completes the target, notifying the source, once all completion conditions are met.</summary>
|
||||
private void CompleteTargetIfPossible()
|
||||
{
|
||||
Common.ContractAssertMonitorStatus(IncomingLock, held: true);
|
||||
if (_targetDecliningPermanently &&
|
||||
!_targetCompletionReserved &&
|
||||
(_boundingState == null || _boundingState.TaskForInputProcessing == null))
|
||||
{
|
||||
_targetCompletionReserved = true;
|
||||
|
||||
// If we're in bounding mode and we have any postponed messages, we need to clear them,
|
||||
// which means calling back to the source, which means we need to escape the incoming lock.
|
||||
if (_boundingState != null && _boundingState.PostponedMessages.Count > 0)
|
||||
{
|
||||
Task.Factory.StartNew(state =>
|
||||
{
|
||||
var thisBufferBlock = (BufferBlock<T>)state;
|
||||
|
||||
// Release any postponed messages
|
||||
List<Exception> exceptions = null;
|
||||
if (thisBufferBlock._boundingState != null)
|
||||
{
|
||||
// Note: No locks should be held at this point
|
||||
Common.ReleaseAllPostponedMessages(thisBufferBlock,
|
||||
thisBufferBlock._boundingState.PostponedMessages,
|
||||
ref exceptions);
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
// It is important to migrate these exceptions to the source part of the owning batch,
|
||||
// because that is the completion task that is publically exposed.
|
||||
thisBufferBlock._source.AddExceptions(exceptions);
|
||||
}
|
||||
|
||||
thisBufferBlock._source.Complete();
|
||||
}, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
|
||||
}
|
||||
// Otherwise, we can just decline the source directly.
|
||||
else
|
||||
{
|
||||
_source.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of messages in the buffer. This must only be used from the debugger as it avoids taking necessary locks.</summary>
|
||||
private int CountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="ToString"]/*' />
|
||||
public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); }
|
||||
|
||||
/// <summary>The data to display in the debugger display attribute.</summary>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
|
||||
private object DebuggerDisplayContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}, Count={1}",
|
||||
Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
|
||||
CountForDebugger);
|
||||
}
|
||||
}
|
||||
/// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
|
||||
object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
|
||||
|
||||
/// <summary>Provides a debugger type proxy for the BufferBlock.</summary>
|
||||
private sealed class DebugView
|
||||
{
|
||||
/// <summary>The buffer block.</summary>
|
||||
private readonly BufferBlock<T> _bufferBlock;
|
||||
/// <summary>The buffer's source half.</summary>
|
||||
private readonly SourceCore<T>.DebuggingInformation _sourceDebuggingInformation;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="bufferBlock">The BufferBlock being viewed.</param>
|
||||
public DebugView(BufferBlock<T> bufferBlock)
|
||||
{
|
||||
Contract.Requires(bufferBlock != null, "Need a block with which to construct the debug view.");
|
||||
_bufferBlock = bufferBlock;
|
||||
_sourceDebuggingInformation = bufferBlock._source.GetDebuggingInformation();
|
||||
}
|
||||
|
||||
/// <summary>Gets the collection of postponed message headers.</summary>
|
||||
public QueuedMap<ISourceBlock<T>, DataflowMessageHeader> PostponedMessages
|
||||
{
|
||||
get { return _bufferBlock._boundingState != null ? _bufferBlock._boundingState.PostponedMessages : null; }
|
||||
}
|
||||
/// <summary>Gets the messages in the buffer.</summary>
|
||||
public IEnumerable<T> Queue { get { return _sourceDebuggingInformation.OutputQueue; } }
|
||||
|
||||
/// <summary>The task used to process messages.</summary>
|
||||
public Task TaskForInputProcessing { get { return _bufferBlock._boundingState != null ? _bufferBlock._boundingState.TaskForInputProcessing : null; } }
|
||||
/// <summary>Gets the task being used for output processing.</summary>
|
||||
public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } }
|
||||
|
||||
/// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
|
||||
public DataflowBlockOptions DataflowBlockOptions { get { return _sourceDebuggingInformation.DataflowBlockOptions; } }
|
||||
|
||||
/// <summary>Gets whether the block is declining further messages.</summary>
|
||||
public bool IsDecliningPermanently { get { return _bufferBlock._targetDecliningPermanently; } }
|
||||
/// <summary>Gets whether the block is completed.</summary>
|
||||
public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } }
|
||||
/// <summary>Gets the block's Id.</summary>
|
||||
public int Id { get { return Common.GetBlockId(_bufferBlock); } }
|
||||
|
||||
/// <summary>Gets the set of all targets linked from this block.</summary>
|
||||
public TargetRegistry<T> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
|
||||
/// <summary>Gets the set of all targets linked from this block.</summary>
|
||||
public ITargetBlock<T> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,427 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// TransformBlock.cs
|
||||
//
|
||||
//
|
||||
// A propagator block that runs a function on each input to produce a single output.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Threading.Tasks.Dataflow.Internal;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow
|
||||
{
|
||||
/// <summary>Provides a dataflow block that invokes a provided <see cref="System.Func{TInput,TOutput}"/> delegate for every data element received.</summary>
|
||||
/// <typeparam name="TInput">Specifies the type of data received and operated on by this <see cref="TransformBlock{TInput,TOutput}"/>.</typeparam>
|
||||
/// <typeparam name="TOutput">Specifies the type of data output by this <see cref="TransformBlock{TInput,TOutput}"/>.</typeparam>
|
||||
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
|
||||
[DebuggerTypeProxy(typeof(TransformBlock<,>.DebugView))]
|
||||
public sealed class TransformBlock<TInput, TOutput> : IPropagatorBlock<TInput, TOutput>, IReceivableSourceBlock<TOutput>, IDebuggerDisplay
|
||||
{
|
||||
/// <summary>The target side.</summary>
|
||||
private readonly TargetCore<TInput> _target;
|
||||
/// <summary>Buffer used to reorder outputs that may have completed out-of-order between the target half and the source half.</summary>
|
||||
private readonly ReorderingBuffer<TOutput> _reorderingBuffer;
|
||||
/// <summary>The source side.</summary>
|
||||
private readonly SourceCore<TOutput> _source;
|
||||
|
||||
/// <summary>Initializes the <see cref="TransformBlock{TInput,TOutput}"/> with the specified <see cref="System.Func{TInput,TOutput}"/>.</summary>
|
||||
/// <param name="transform">The function to invoke with each data element received.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="transform"/> is null (Nothing in Visual Basic).</exception>
|
||||
public TransformBlock(Func<TInput, TOutput> transform) :
|
||||
this(transform, null, ExecutionDataflowBlockOptions.Default)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="TransformBlock{TInput,TOutput}"/> with the specified <see cref="System.Func{TInput,TOutput}"/> and
|
||||
/// <see cref="ExecutionDataflowBlockOptions"/>.
|
||||
/// </summary>
|
||||
/// <param name="transform">The function to invoke with each data element received.</param>
|
||||
/// <param name="dataflowBlockOptions">The options with which to configure this <see cref="TransformBlock{TInput,TOutput}"/>.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="transform"/> is null (Nothing in Visual Basic).</exception>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
|
||||
public TransformBlock(Func<TInput, TOutput> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) :
|
||||
this(transform, null, dataflowBlockOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>Initializes the <see cref="TransformBlock{TInput,TOutput}"/> with the specified <see cref="System.Func{TInput,TOutput}"/>.</summary>
|
||||
/// <param name="transform">The function to invoke with each data element received.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="transform"/> is null (Nothing in Visual Basic).</exception>
|
||||
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
|
||||
public TransformBlock(Func<TInput, Task<TOutput>> transform) :
|
||||
this(null, transform, ExecutionDataflowBlockOptions.Default)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="TransformBlock{TInput,TOutput}"/> with the specified <see cref="System.Func{TInput,TOutput}"/>
|
||||
/// and <see cref="ExecutionDataflowBlockOptions"/>.
|
||||
/// </summary>
|
||||
/// <param name="transform">The function to invoke with each data element received.</param>
|
||||
/// <param name="dataflowBlockOptions">The options with which to configure this <see cref="TransformBlock{TInput,TOutput}"/>.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="transform"/> is null (Nothing in Visual Basic).</exception>
|
||||
/// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
|
||||
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
|
||||
public TransformBlock(Func<TInput, Task<TOutput>> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) :
|
||||
this(null, transform, dataflowBlockOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="TransformBlock{TInput,TOutput}"/> with the specified <see cref="System.Func{TInput,TOutput}"/>
|
||||
/// and <see cref="DataflowBlockOptions"/>.
|
||||
/// </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="TransformBlock{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 TransformBlock(Func<TInput, TOutput> transformSync, Func<TInput, Task<TOutput>> transformAsync, ExecutionDataflowBlockOptions dataflowBlockOptions)
|
||||
{
|
||||
if (transformSync == null && transformAsync == null) throw new ArgumentNullException("transform");
|
||||
if (dataflowBlockOptions == null) throw new ArgumentNullException("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) => ((TransformBlock<TInput, TOutput>)owningSource)._target.ChangeBoundingCount(-count);
|
||||
|
||||
// Initialize source component.
|
||||
_source = new SourceCore<TOutput>(this, dataflowBlockOptions,
|
||||
owningSource => ((TransformBlock<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
|
||||
if (dataflowBlockOptions.SupportsParallelExecution)
|
||||
{
|
||||
_reorderingBuffer = new ReorderingBuffer<TOutput>(this, (owningSource, message) => ((TransformBlock<TInput, TOutput>)owningSource)._source.AddMessage(message));
|
||||
}
|
||||
|
||||
// Create the underlying target
|
||||
if (transformSync != null) // sync
|
||||
{
|
||||
_target = new TargetCore<TInput>(this,
|
||||
messageWithId => ProcessMessage(transformSync, messageWithId),
|
||||
_reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.None);
|
||||
}
|
||||
else // async
|
||||
{
|
||||
Debug.Assert(transformAsync != null, "Incorrect delegate type.");
|
||||
_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 = ((TransformBlock<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
|
||||
}
|
||||
|
||||
/// <summary>Processes the message with a user-provided transform function that returns a TOutput.</summary>
|
||||
/// <param name="transform">The transform function to use to process the message.</param>
|
||||
/// <param name="messageWithId">The message to be processed.</param>
|
||||
private void ProcessMessage(Func<TInput, TOutput> transform, KeyValuePair<TInput, long> messageWithId)
|
||||
{
|
||||
// Process the input message to get the output message
|
||||
TOutput outputItem = default(TOutput);
|
||||
bool itemIsValid = false;
|
||||
try
|
||||
{
|
||||
outputItem = transform(messageWithId.Key);
|
||||
itemIsValid = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
// If this exception represents cancellation, swallow it rather than shutting down the block.
|
||||
if (!Common.IsCooperativeCancellation(exc)) throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// If we were not successful in producing an item, update the bounding
|
||||
// count to reflect that we're done with this input item.
|
||||
if (!itemIsValid) _target.ChangeBoundingCount(-1);
|
||||
|
||||
// If there's no reordering buffer (because we're running sequentially),
|
||||
// simply pass the output message through. Otherwise, there's a reordering buffer,
|
||||
// so add to it instead (if a reordering buffer is used, we always need
|
||||
// to output the message to it, even if the operation failed and outputMessage
|
||||
// is null... this is because the reordering buffer cares about a strict sequence
|
||||
// of IDs, and it needs to know when a particular ID has completed. It will eliminate
|
||||
// null messages accordingly.)
|
||||
if (_reorderingBuffer == null)
|
||||
{
|
||||
if (itemIsValid) _source.AddMessage(outputItem);
|
||||
}
|
||||
else _reorderingBuffer.AddItem(messageWithId.Value, outputItem, itemIsValid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Processes the message with a user-provided transform function that returns a task of TOutput.</summary>
|
||||
/// <param name="transform">The transform function to use to process the message.</param>
|
||||
/// <param name="messageWithId">The message to be processed.</param>
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
private void ProcessMessageWithTask(Func<TInput, Task<TOutput>> transform, KeyValuePair<TInput, long> messageWithId)
|
||||
{
|
||||
Contract.Requires(transform != null, "Function to invoke is required.");
|
||||
|
||||
// Run the transform function to get the task that represents the operation's completion
|
||||
Task<TOutput> task = null;
|
||||
Exception caughtException = null;
|
||||
try
|
||||
{
|
||||
task = transform(messageWithId.Key);
|
||||
}
|
||||
catch (Exception exc) { caughtException = exc; }
|
||||
|
||||
// If no task is available, we're done.
|
||||
if (task == null)
|
||||
{
|
||||
// If we didn't get a task because an exception occurred,
|
||||
// store it (if the exception was cancellation, just ignore it).
|
||||
if (caughtException != null && !Common.IsCooperativeCancellation(caughtException))
|
||||
{
|
||||
Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key);
|
||||
_target.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false);
|
||||
}
|
||||
|
||||
// If there's a reordering buffer, notify it that this message is done.
|
||||
if (_reorderingBuffer != null) _reorderingBuffer.IgnoreItem(messageWithId.Value);
|
||||
|
||||
// Signal that we're done this async operation, and remove the bounding
|
||||
// count for the input item that didn't yield any output.
|
||||
_target.SignalOneAsyncMessageCompleted(boundingCountChange: -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, join with the asynchronous operation when it completes.
|
||||
task.ContinueWith((completed, state) =>
|
||||
{
|
||||
var tuple = (Tuple<TransformBlock<TInput, TOutput>, KeyValuePair<TInput, long>>)state;
|
||||
tuple.Item1.AsyncCompleteProcessMessageWithTask(completed, tuple.Item2);
|
||||
}, Tuple.Create(this, messageWithId), CancellationToken.None,
|
||||
Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default);
|
||||
}
|
||||
|
||||
/// <summary>Completes the processing of an asynchronous message.</summary>
|
||||
/// <param name="completed">The completed task storing the output data generated for an input message.</param>
|
||||
/// <param name="messageWithId">The originating message</param>
|
||||
private void AsyncCompleteProcessMessageWithTask(Task<TOutput> completed, KeyValuePair<TInput, long> messageWithId)
|
||||
{
|
||||
Contract.Requires(completed != null, "Completed task is required.");
|
||||
Contract.Requires(completed.IsCompleted, "Task must be completed to be here.");
|
||||
|
||||
bool isBounded = _target.IsBounded;
|
||||
bool gotOutputItem = false;
|
||||
TOutput outputItem = default(TOutput);
|
||||
|
||||
switch (completed.Status)
|
||||
{
|
||||
case TaskStatus.RanToCompletion:
|
||||
outputItem = completed.Result;
|
||||
gotOutputItem = true;
|
||||
break;
|
||||
|
||||
case TaskStatus.Faulted:
|
||||
// We must add the exception before declining and signaling completion, as the exception
|
||||
// is part of the operation, and the completion conditions depend on this.
|
||||
AggregateException aggregate = completed.Exception;
|
||||
Common.StoreDataflowMessageValueIntoExceptionData(aggregate, messageWithId.Key, targetInnerExceptions: true);
|
||||
_target.Complete(aggregate, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: true);
|
||||
break;
|
||||
// Nothing special to do for cancellation
|
||||
}
|
||||
|
||||
// Adjust the bounding count if necessary (we only need to decrement it for faulting
|
||||
// and cancellation, since in the case of success we still have an item that's now in the output buffer).
|
||||
// Even though this is more costly (again, only in the non-success case, we do this before we store the
|
||||
// message, so that if there's a race to remove the element from the source buffer, the count is
|
||||
// appropriately incremented before it's decremented.
|
||||
if (!gotOutputItem && isBounded) _target.ChangeBoundingCount(-1);
|
||||
|
||||
// If there's no reordering buffer (because we're running sequentially),
|
||||
// and we got a message, simply pass the output message through.
|
||||
if (_reorderingBuffer == null)
|
||||
{
|
||||
if (gotOutputItem) _source.AddMessage(outputItem);
|
||||
}
|
||||
// Otherwise, there's a reordering buffer, so add to it instead.
|
||||
// Even if something goes wrong, we need to update the
|
||||
// reordering buffer, so it knows that an item isn't missing.
|
||||
else _reorderingBuffer.AddItem(messageWithId.Value, outputItem, itemIsValid: gotOutputItem);
|
||||
|
||||
// Let the target know that one of the asynchronous operations it launched has completed.
|
||||
_target.SignalOneAsyncMessageCompleted();
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
|
||||
public void Complete() { _target.Complete(exception: null, dropPendingMessages: false); }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
|
||||
void IDataflowBlock.Fault(Exception exception)
|
||||
{
|
||||
if (exception == null) throw new ArgumentNullException("exception");
|
||||
Contract.EndContractBlock();
|
||||
|
||||
_target.Complete(exception, dropPendingMessages: true);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
|
||||
public IDisposable LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions)
|
||||
{
|
||||
return _source.LinkTo(target, linkOptions);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
|
||||
public Boolean TryReceive(Predicate<TOutput> filter, out TOutput item)
|
||||
{
|
||||
return _source.TryReceive(filter, out item);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
|
||||
public bool TryReceiveAll(out IList<TOutput> items) { return _source.TryReceiveAll(out items); }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
|
||||
public Task Completion { get { return _source.Completion; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="InputCount"]/*' />
|
||||
public int InputCount { get { return _target.InputCount; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="OutputCount"]/*' />
|
||||
public int OutputCount { get { return _source.OutputCount; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
|
||||
DataflowMessageStatus ITargetBlock<TInput>.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, Boolean consumeToAccept)
|
||||
{
|
||||
return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
|
||||
TOutput ISourceBlock<TOutput>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out Boolean messageConsumed)
|
||||
{
|
||||
return _source.ConsumeMessage(messageHeader, target, out messageConsumed);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
|
||||
bool ISourceBlock<TOutput>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
|
||||
{
|
||||
return _source.ReserveMessage(messageHeader, target);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
|
||||
void ISourceBlock<TOutput>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
|
||||
{
|
||||
_source.ReleaseReservation(messageHeader, target);
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks.</summary>
|
||||
private int InputCountForDebugger { get { return _target.GetDebuggingInformation().InputCount; } }
|
||||
/// <summary>Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks.</summary>
|
||||
private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="ToString"]/*' />
|
||||
public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); }
|
||||
|
||||
/// <summary>The data to display in the debugger display attribute.</summary>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
|
||||
private object DebuggerDisplayContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}, InputCount={1}, OutputCount={2}",
|
||||
Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
|
||||
InputCountForDebugger,
|
||||
OutputCountForDebugger);
|
||||
}
|
||||
}
|
||||
/// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
|
||||
object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
|
||||
|
||||
/// <summary>Provides a debugger type proxy for the TransformBlock.</summary>
|
||||
private sealed class DebugView
|
||||
{
|
||||
/// <summary>The transform being viewed.</summary>
|
||||
private readonly TransformBlock<TInput, TOutput> _transformBlock;
|
||||
/// <summary>The target half of the block being viewed.</summary>
|
||||
private readonly TargetCore<TInput>.DebuggingInformation _targetDebuggingInformation;
|
||||
/// <summary>The source half of the block being viewed.</summary>
|
||||
private readonly SourceCore<TOutput>.DebuggingInformation _sourceDebuggingInformation;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="transformBlock">The transform being viewed.</param>
|
||||
public DebugView(TransformBlock<TInput, TOutput> transformBlock)
|
||||
{
|
||||
Contract.Requires(transformBlock != null, "Need a block with which to construct the debug view.");
|
||||
_transformBlock = transformBlock;
|
||||
_targetDebuggingInformation = transformBlock._target.GetDebuggingInformation();
|
||||
_sourceDebuggingInformation = transformBlock._source.GetDebuggingInformation();
|
||||
}
|
||||
|
||||
/// <summary>Gets the messages waiting to be processed.</summary>
|
||||
public IEnumerable<TInput> InputQueue { get { return _targetDebuggingInformation.InputQueue; } }
|
||||
/// <summary>Gets any postponed messages.</summary>
|
||||
public QueuedMap<ISourceBlock<TInput>, DataflowMessageHeader> PostponedMessages { get { return _targetDebuggingInformation.PostponedMessages; } }
|
||||
/// <summary>Gets the messages waiting to be received.</summary>
|
||||
public IEnumerable<TOutput> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } }
|
||||
|
||||
/// <summary>Gets the number of outstanding input operations.</summary>
|
||||
public Int32 CurrentDegreeOfParallelism { get { return _targetDebuggingInformation.CurrentDegreeOfParallelism; } }
|
||||
/// <summary>Gets the task being used for output processing.</summary>
|
||||
public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } }
|
||||
|
||||
/// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
|
||||
public ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _targetDebuggingInformation.DataflowBlockOptions; } }
|
||||
/// <summary>Gets whether the block is declining further messages.</summary>
|
||||
public bool IsDecliningPermanently { get { return _targetDebuggingInformation.IsDecliningPermanently; } }
|
||||
/// <summary>Gets whether the block is completed.</summary>
|
||||
public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } }
|
||||
/// <summary>Gets the block's Id.</summary>
|
||||
public int Id { get { return Common.GetBlockId(_transformBlock); } }
|
||||
|
||||
/// <summary>Gets the set of all targets linked from this block.</summary>
|
||||
public TargetRegistry<TOutput> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
|
||||
/// <summary>Gets the target that holds a reservation on the next message, if any.</summary>
|
||||
public ITargetBlock<TOutput> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user