--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// DataflowBlock.cs
+//
+//
+// Common functionality for ITargetBlock, ISourceBlock, and IPropagatorBlock.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.ExceptionServices;
+using System.Security;
+using System.Threading.Tasks.Dataflow.Internal;
+using System.Threading.Tasks.Dataflow.Internal.Threading;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>
+ /// Provides a set of static (Shared in Visual Basic) methods for working with dataflow blocks.
+ /// </summary>
+ public static class DataflowBlock
+ {
+ #region LinkTo
+ /// <summary>Links the <see cref="ISourceBlock{TOutput}"/> to the specified <see cref="ITargetBlock{TOutput}"/>.</summary>
+ /// <param name="source">The source from which to link.</param>
+ /// <param name="target">The <see cref="ITargetBlock{TOutput}"/> to which to connect the source.</param>
+ /// <returns>An IDisposable that, upon calling Dispose, will unlink the source from the target.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
+ public static IDisposable LinkTo<TOutput>(
+ this ISourceBlock<TOutput> source,
+ ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (source == null) throw new ArgumentNullException("source");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // This method exists purely to pass default DataflowLinkOptions
+ // to increase usability of the "90%" case.
+ return source.LinkTo(target, DataflowLinkOptions.Default);
+ }
+
+ /// <summary>Links the <see cref="ISourceBlock{TOutput}"/> to the specified <see cref="ITargetBlock{TOutput}"/> using the specified filter.</summary>
+ /// <param name="source">The source from which to link.</param>
+ /// <param name="target">The <see cref="ITargetBlock{TOutput}"/> to which to connect the source.</param>
+ /// <param name="predicate">The filter a message must pass in order for it to propagate from the source to the target.</param>
+ /// <returns>An IDisposable that, upon calling Dispose, will unlink the source from the target.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> is null (Nothing in Visual Basic).</exception>
+ public static IDisposable LinkTo<TOutput>(
+ this ISourceBlock<TOutput> source,
+ ITargetBlock<TOutput> target,
+ Predicate<TOutput> predicate)
+ {
+ // All argument validation handled by delegated method.
+ return LinkTo(source, target, DataflowLinkOptions.Default, predicate);
+ }
+
+ /// <summary>Links the <see cref="ISourceBlock{TOutput}"/> to the specified <see cref="ITargetBlock{TOutput}"/> using the specified filter.</summary>
+ /// <param name="source">The source from which to link.</param>
+ /// <param name="target">The <see cref="ITargetBlock{TOutput}"/> to which to connect the source.</param>
+ /// <param name="predicate">The filter a message must pass in order for it to propagate from the source to the target.</param>
+ /// <param name="linkOptions">The options to use to configure the link.</param>
+ /// <returns>An IDisposable that, upon calling Dispose, will unlink the source from the target.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="linkOptions"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> is null (Nothing in Visual Basic).</exception>
+ public static IDisposable LinkTo<TOutput>(
+ this ISourceBlock<TOutput> source,
+ ITargetBlock<TOutput> target,
+ DataflowLinkOptions linkOptions,
+ Predicate<TOutput> predicate)
+ {
+ // Validate arguments
+ if (source == null) throw new ArgumentNullException("source");
+ if (target == null) throw new ArgumentNullException("target");
+ if (linkOptions == null) throw new ArgumentNullException("linkOptions");
+ if (predicate == null) throw new ArgumentNullException("predicate");
+ Contract.EndContractBlock();
+
+ // Create the filter, which links to the real target, and then
+ // link the real source to this intermediate filter.
+ var filter = new FilteredLinkPropagator<TOutput>(source, target, predicate);
+ return source.LinkTo(filter, linkOptions);
+ }
+
+ /// <summary>Provides a synchronous filter for use in filtered LinkTos.</summary>
+ /// <typeparam name="T">Specifies the type of data being filtered.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(FilteredLinkPropagator<>.DebugView))]
+ private sealed class FilteredLinkPropagator<T> : IPropagatorBlock<T, T>, IDebuggerDisplay
+ {
+ /// <summary>The source connected with this filter.</summary>
+ private readonly ISourceBlock<T> _source;
+ /// <summary>The target with which this block is associated.</summary>
+ private readonly ITargetBlock<T> _target;
+ /// <summary>The predicate provided by the user.</summary>
+ private readonly Predicate<T> _userProvidedPredicate;
+
+ /// <summary>Initializes the filter passthrough.</summary>
+ /// <param name="source">The source connected to this filter.</param>
+ /// <param name="target">The target to which filtered messages should be passed.</param>
+ /// <param name="predicate">The predicate to run for each messsage.</param>
+ internal FilteredLinkPropagator(ISourceBlock<T> source, ITargetBlock<T> target, Predicate<T> predicate)
+ {
+ Contract.Requires(source != null, "Filtered link requires a source to filter on.");
+ Contract.Requires(target != null, "Filtered link requires a target to filter to.");
+ Contract.Requires(predicate != null, "Filtered link requires a predicate to filter with.");
+
+ // Store the arguments
+ _source = source;
+ _target = target;
+ _userProvidedPredicate = predicate;
+ }
+
+ /// <summary>Runs the user-provided predicate over an item in the correct execution context.</summary>
+ /// <param name="item">The item to evaluate.</param>
+ /// <returns>true if the item passed the filter; otherwise, false.</returns>
+ private bool RunPredicate(T item)
+ {
+ Contract.Requires(_userProvidedPredicate != null, "User-provided predicate is required.");
+
+ return _userProvidedPredicate(item); // avoid state object allocation if execution context isn't needed
+ }
+
+ /// <summary>Manually closes over state necessary in FilteredLinkPropagator.</summary>
+ private sealed class PredicateContextState
+ {
+ /// <summary>The input to be filtered.</summary>
+ internal readonly T Input;
+ /// <summary>The predicate function.</summary>
+ internal readonly Predicate<T> Predicate;
+ /// <summary>The result of the filtering operation.</summary>
+ internal bool Output;
+
+ /// <summary>Initializes the predicate state.</summary>
+ /// <param name="input">The input to be filtered.</param>
+ /// <param name="predicate">The predicate function.</param>
+ internal PredicateContextState(T input, Predicate<T> predicate)
+ {
+ Contract.Requires(predicate != null, "A predicate with which to filter is required.");
+ this.Input = input;
+ this.Predicate = predicate;
+ }
+
+ /// <summary>Runs the predicate function over the input and stores the result into the output.</summary>
+ internal void Run()
+ {
+ Contract.Requires(Predicate != null, "Non-null predicate required");
+ Output = Predicate(Input);
+ }
+ }
+
+ /// <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. Some targets may have a null source, but FilteredLinkPropagator
+ // is an internal target that should only ever have source non-null.
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (source == null) throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Run the filter.
+ bool passedFilter = RunPredicate(messageValue);
+
+ // If the predicate matched, pass the message along to the real target.
+ if (passedFilter)
+ {
+ return _target.OfferMessage(messageHeader, messageValue, this, consumeToAccept);
+ }
+ // Otherwise, decline.
+ else return DataflowMessageStatus.Declined;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ T ISourceBlock<T>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target, out Boolean messageConsumed)
+ {
+ // This message should have only made it to the target if it passes the filter, so we shouldn't need to check again.
+ // The real source will also be doing verifications, so we don't need to validate args here.
+ Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed.");
+ return _source.ConsumeMessage(messageHeader, this, out messageConsumed);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ bool ISourceBlock<T>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
+ {
+ // This message should have only made it to the target if it passes the filter, so we shouldn't need to check again.
+ // The real source will also be doing verifications, so we don't need to validate args here.
+ Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed.");
+ return _source.ReserveMessage(messageHeader, this);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<T>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
+ {
+ // This message should have only made it to the target if it passes the filter, so we shouldn't need to check again.
+ // The real source will also be doing verifications, so we don't need to validate args here.
+ Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed.");
+ _source.ReleaseReservation(messageHeader, this);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { return _source.Completion; } }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete() { _target.Complete(); }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void IDataflowBlock.Fault(Exception exception) { _target.Fault(exception); }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ IDisposable ISourceBlock<T>.LinkTo(ITargetBlock<T> target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displaySource = _source as IDebuggerDisplay;
+ var displayTarget = _target as IDebuggerDisplay;
+ return string.Format("{0} Source=\"{1}\", Target=\"{2}\"",
+ Common.GetNameForDebugger(this),
+ displaySource != null ? displaySource.Content : _source,
+ displayTarget != null ? displayTarget.Content : _target);
+ }
+ }
+ /// <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 a filter.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The filter.</summary>
+ private readonly FilteredLinkPropagator<T> _filter;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="filter">The filter to view.</param>
+ public DebugView(FilteredLinkPropagator<T> filter)
+ {
+ Contract.Requires(filter != null, "Need a filter with which to construct the debug view.");
+ _filter = filter;
+ }
+
+ /// <summary>The linked target for this filter.</summary>
+ public ITargetBlock<T> LinkedTarget { get { return _filter._target; } }
+ }
+ }
+ #endregion
+
+ #region Post and SendAsync
+ /// <summary>Posts an item to the <see cref="T:System.Threading.Tasks.Dataflow.ITargetBlock`1"/>.</summary>
+ /// <typeparam name="TInput">Specifies the type of data accepted by the target block.</typeparam>
+ /// <param name="target">The target block.</param>
+ /// <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>
+ public static Boolean Post<TInput>(this ITargetBlock<TInput> target, TInput item)
+ {
+ if (target == null) throw new ArgumentNullException("target");
+ return target.OfferMessage(Common.SingleMessageHeader, item, source: null, consumeToAccept: false) == DataflowMessageStatus.Accepted;
+ }
+
+ /// <summary>Asynchronously offers a message to the target message block, allowing for postponement.</summary>
+ /// <typeparam name="TInput">Specifies the type of the data to post to the target.</typeparam>
+ /// <param name="target">The target to which to post the data.</param>
+ /// <param name="item">The item being offered to the target.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{Boolean}"/> that represents the asynchronous send. If the target
+ /// accepts and consumes the offered element during the call to SendAsync, upon return
+ /// from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see>
+ /// property will return true. If the target declines the offered element during the call, upon return from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will
+ /// be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see> property will return false. If the target
+ /// postpones the offered element, the element will be buffered until such time that the target consumes or releases it, at which
+ /// point the Task will complete, with its <see cref="System.Threading.Tasks.Task{Boolean}.Result"/> indicating whether the message was consumed. If the target
+ /// never attempts to consume or release the message, the returned task will never complete.
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
+ public static Task<Boolean> SendAsync<TInput>(this ITargetBlock<TInput> target, TInput item)
+ {
+ return SendAsync<TInput>(target, item, CancellationToken.None);
+ }
+
+ /// <summary>Asynchronously offers a message to the target message block, allowing for postponement.</summary>
+ /// <typeparam name="TInput">Specifies the type of the data to post to the target.</typeparam>
+ /// <param name="target">The target to which to post the data.</param>
+ /// <param name="item">The item being offered to the target.</param>
+ /// <param name="cancellationToken">The cancellation token with which to request cancellation of the send operation.</param>
+ /// <returns>
+ /// <para>
+ /// A <see cref="System.Threading.Tasks.Task{Boolean}"/> that represents the asynchronous send. If the target
+ /// accepts and consumes the offered element during the call to SendAsync, upon return
+ /// from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see>
+ /// property will return true. If the target declines the offered element during the call, upon return from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will
+ /// be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see> property will return false. If the target
+ /// postpones the offered element, the element will be buffered until such time that the target consumes or releases it, at which
+ /// point the Task will complete, with its <see cref="System.Threading.Tasks.Task{Boolean}.Result"/> indicating whether the message was consumed. If the target
+ /// never attempts to consume or release the message, the returned task will never complete.
+ /// </para>
+ /// <para>
+ /// If cancellation is requested before the target has successfully consumed the sent data,
+ /// the returned task will complete in the Canceled state and the data will no longer be available to the target.
+ /// </para>
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ public static Task<Boolean> SendAsync<TInput>(this ITargetBlock<TInput> target, TInput item, CancellationToken cancellationToken)
+ {
+ // Validate arguments. No validation necessary for item.
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // Fast path check for cancellation
+ if (cancellationToken.IsCancellationRequested)
+ return Common.CreateTaskFromCancellation<Boolean>(cancellationToken);
+
+ SendAsyncSource<TInput> source;
+
+ // Fast path: try to offer the item synchronously. This first try is done
+ // without any form of cancellation, and thus consumeToAccept can be the better-performing "false".
+ try
+ {
+ switch (target.OfferMessage(Common.SingleMessageHeader, item, source: null, consumeToAccept: false))
+ {
+ // If the message is immediately accepted, return a cached completed task with a true result
+ case DataflowMessageStatus.Accepted:
+ return Common.CompletedTaskWithTrueResult;
+
+ // If the target is declining permanently, return a cached completed task with a false result
+ case DataflowMessageStatus.DecliningPermanently:
+ return Common.CompletedTaskWithFalseResult;
+
+#if DEBUG
+ case DataflowMessageStatus.Postponed:
+ Debug.Assert(false, "A message should never be postponed when no source has been provided");
+ break;
+
+ case DataflowMessageStatus.NotAvailable:
+ Debug.Assert(false, "The message should never be missed, as it's offered to only this one target");
+ break;
+#endif
+ }
+
+ // Slow path: the target did not accept the synchronous post, nor did it decline it.
+ // Create a source for the send, launch the offering, and return the representative task.
+ // This ctor attempts to register a cancellation notification which would throw if the
+ // underlying CTS has been disposed of. Therefore, keep it inside the try/catch block.
+ source = new SendAsyncSource<TInput>(target, item, cancellationToken);
+ }
+ catch (Exception exc)
+ {
+ // If the target throws from OfferMessage, return a faulted task
+ Common.StoreDataflowMessageValueIntoExceptionData(exc, item);
+ return Common.CreateTaskFromException<Boolean>(exc);
+ }
+
+ Debug.Assert(source != null, "The SendAsyncSource instance must have been constructed.");
+ source.OfferToTarget(); // synchronous to preserve message ordering
+ return source.Task;
+ }
+
+ /// <summary>
+ /// Provides a source used by SendAsync that will buffer a single message and signal when it's been accepted or declined.
+ /// </summary>
+ /// <remarks>This source must only be passed to a single target, and must only be used once.</remarks>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(SendAsyncSource<>.DebugView))]
+ private sealed class SendAsyncSource<TOutput> : TaskCompletionSource<Boolean>, ISourceBlock<TOutput>, IDebuggerDisplay
+ {
+ /// <summary>The target to offer to.</summary>
+ private readonly ITargetBlock<TOutput> _target;
+ /// <summary>The buffered message.</summary>
+ private readonly TOutput _messageValue;
+
+ /// <summary>CancellationToken used to cancel the send.</summary>
+ private CancellationToken _cancellationToken;
+ /// <summary>Registration with the cancellation token.</summary>
+ private CancellationTokenRegistration _cancellationRegistration;
+ /// <summary>The cancellation/completion state of the source.</summary>
+ private int _cancellationState; // one of the CANCELLATION_STATE_* constant values, defaulting to NONE
+
+ // Cancellation states:
+ // _cancellationState starts out as NONE, and will remain that way unless a CancellationToken
+ // is provided in the initial OfferToTarget call. As such, unless a token is provided,
+ // all synchronization related to cancellation will be avoided. Once a token is provided,
+ // the state transitions to REGISTERED. If cancellation then is requested or if the target
+ // calls back to consume the message, the state will transition to COMPLETING prior to
+ // actually committing the action; if it can't transition to COMPLETING, then the action doesn't
+ // take effect (e.g. if cancellation raced with the target consuming, such that the cancellation
+ // action was able to transition to COMPLETING but the consumption wasn't, then ConsumeMessage
+ // would return false indicating that the message could not be consumed). The only additional
+ // complication here is around reservations. If a target reserves a message, _cancellationState
+ // transitions to RESERVED. A subsequent ConsumeMessage call can successfully transition from
+ // RESERVED to COMPLETING, but cancellation can't; cancellation can only transition from REGISTERED
+ // to COMPLETING. If the reservation on the message is instead released, _cancellationState
+ // will transition back to REGISTERED.
+
+ /// <summary>No cancellation registration is used.</summary>
+ private const int CANCELLATION_STATE_NONE = 0;
+ /// <summary>A cancellation token has been registered.</summary>
+ private const int CANCELLATION_STATE_REGISTERED = 1;
+ /// <summary>The message has been reserved. Only used if a cancellation token is in play.</summary>
+ private const int CANCELLATION_STATE_RESERVED = 2;
+ /// <summary>Completion is now in progress. Only used if a cancellation token is in play.</summary>
+ private const int CANCELLATION_STATE_COMPLETING = 3;
+
+ /// <summary>Initializes the source.</summary>
+ /// <param name="target">The target to offer to.</param>
+ /// <param name="messageValue">The message to offer and buffer.</param>
+ /// <param name="cancellationToken">The cancellation token with which to cancel the send.</param>
+ internal SendAsyncSource(ITargetBlock<TOutput> target, TOutput messageValue, CancellationToken cancellationToken)
+ {
+ Contract.Requires(target != null, "A valid target to send to is required.");
+ _target = target;
+ _messageValue = messageValue;
+
+ // If a cancelable CancellationToken is used, update our cancellation state
+ // and register with the token. Only if CanBeCanceled is true due we want
+ // to pay the subsequent costs around synchronization between cancellation
+ // requests and the target coming back to consume the message.
+ if (cancellationToken.CanBeCanceled)
+ {
+ _cancellationToken = cancellationToken;
+ _cancellationState = CANCELLATION_STATE_REGISTERED;
+
+ try
+ {
+ _cancellationRegistration = cancellationToken.Register(
+ _cancellationCallback, new WeakReference<SendAsyncSource<TOutput>>(this));
+ }
+ catch
+ {
+ // Suppress finalization. Finalization is only required if the target drops a reference
+ // to the source before the source has completed, and we'll never offer to the target.
+ GC.SuppressFinalize(this);
+
+ // Propagate the exception
+ throw;
+ }
+ }
+ }
+
+ /// <summary>Finalizer that completes the returned task if all references to this source are dropped.</summary>
+ ~SendAsyncSource()
+ {
+ // CompleteAsDeclined uses synchronization, which is dangerous for a finalizer
+ // during shutdown or appdomain unload.
+ if (!Environment.HasShutdownStarted)
+ {
+ CompleteAsDeclined(runAsync: true);
+ }
+ }
+
+ /// <summary>Completes the source in an "Accepted" state.</summary>
+ /// <param name="runAsync">true to accept asynchronously; false to accept synchronously.</param>
+ private void CompleteAsAccepted(bool runAsync)
+ {
+ RunCompletionAction(state =>
+ {
+ try { ((SendAsyncSource<TOutput>)state).TrySetResult(true); }
+ catch (ObjectDisposedException) { }
+ }, this, runAsync);
+ }
+
+ /// <summary>Completes the source in an "Declined" state.</summary>
+ /// <param name="runAsync">true to decline asynchronously; false to decline synchronously.</param>
+ private void CompleteAsDeclined(bool runAsync)
+ {
+ RunCompletionAction(state =>
+ {
+ // The try/catch for ObjectDisposedException handles the case where the
+ // user disposes of the returned task before we're done with it.
+ try { ((SendAsyncSource<TOutput>)state).TrySetResult(false); }
+ catch (ObjectDisposedException) { }
+ }, this, runAsync);
+ }
+
+ /// <summary>Completes the source in faulted state.</summary>
+ /// <param name="exception">The exception with which to fault.</param>
+ /// <param name="runAsync">true to fault asynchronously; false to fault synchronously.</param>
+ private void CompleteAsFaulted(Exception exception, bool runAsync)
+ {
+ RunCompletionAction(state =>
+ {
+ var tuple = (Tuple<SendAsyncSource<TOutput>, Exception>)state;
+ try { tuple.Item1.TrySetException(tuple.Item2); }
+ catch (ObjectDisposedException) { }
+ }, Tuple.Create<SendAsyncSource<TOutput>, Exception>(this, exception), runAsync);
+ }
+
+ /// <summary>Completes the source in canceled state.</summary>
+ /// <param name="runAsync">true to fault asynchronously; false to fault synchronously.</param>
+ private void CompleteAsCanceled(bool runAsync)
+ {
+ RunCompletionAction(state =>
+ {
+ try { ((SendAsyncSource<TOutput>)state).TrySetCanceled(); }
+ catch (ObjectDisposedException) { }
+ }, this, runAsync);
+ }
+
+ /// <summary>Executes a completion action.</summary>
+ /// <param name="completionAction">The action to execute, passed the state.</param>
+ /// <param name="completionActionState">The state to pass into the delegate.</param>
+ /// <param name="runAsync">true to execute the action asynchronously; false to execute it synchronously.</param>
+ /// <remarks>
+ /// async should be true if this is being called on a path that has the target on the stack, e.g.
+ /// the target is calling to ConsumeMessage. We don't want to block the target indefinitely
+ /// with any synchronous continuations off of the returned send async task.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
+ private void RunCompletionAction(Action<object> completionAction, object completionActionState, bool runAsync)
+ {
+ Contract.Requires(completionAction != null, "Completion action to run is required.");
+
+ // Suppress finalization. Finalization is only required if the target drops a reference
+ // to the source before the source has completed, and here we're completing the source.
+ GC.SuppressFinalize(this);
+
+ // Dispose of the cancellation registration if there is one
+ if (_cancellationState != CANCELLATION_STATE_NONE)
+ {
+ Debug.Assert(_cancellationRegistration != default(CancellationTokenRegistration),
+ "If we're not in NONE, we must have a cancellation token we've registered with.");
+ _cancellationRegistration.Dispose();
+ }
+
+ // If we're meant to run asynchronously, launch a task.
+ if (runAsync)
+ {
+ System.Threading.Tasks.Task.Factory.StartNew(
+ completionAction, completionActionState,
+ CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ // Otherwise, execute directly.
+ else
+ {
+ completionAction(completionActionState);
+ }
+ }
+
+ /// <summary>Offers the message to the target asynchronously.</summary>
+ private void OfferToTargetAsync()
+ {
+ System.Threading.Tasks.Task.Factory.StartNew(
+ state => ((SendAsyncSource<TOutput>)state).OfferToTarget(), this,
+ CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+
+ /// <summary>Cached delegate used to cancel a send in response to a cancellation request.</summary>
+ private readonly static Action<object> _cancellationCallback = CancellationHandler;
+
+ /// <summary>Attempts to cancel the source passed as state in response to a cancellation request.</summary>
+ /// <param name="state">
+ /// A weak reference to the SendAsyncSource. A weak reference is used to prevent the source
+ /// from being rooted in a long-lived token.
+ /// </param>
+ private static void CancellationHandler(object state)
+ {
+ SendAsyncSource<TOutput> source = Common.UnwrapWeakReference<SendAsyncSource<TOutput>>(state);
+ if (source != null)
+ {
+ Debug.Assert(source._cancellationState != CANCELLATION_STATE_NONE,
+ "If cancellation is in play, we must have already moved out of the NONE state.");
+
+ // Try to reserve completion, and if we can, complete as canceled. Note that we can only
+ // achieve cancellation when in the REGISTERED state, and not when in the RESERVED state,
+ // as if a target has reserved the message, we must allow the message to be consumed successfully.
+ if (source._cancellationState == CANCELLATION_STATE_REGISTERED && // fast check to avoid the interlocked if we can
+ Interlocked.CompareExchange(ref source._cancellationState, CANCELLATION_STATE_COMPLETING, CANCELLATION_STATE_REGISTERED) == CANCELLATION_STATE_REGISTERED)
+ {
+ // We've reserved completion, so proceed to cancel the task.
+ source.CompleteAsCanceled(true);
+ }
+ }
+ }
+
+ /// <summary>Offers the message to the target synchronously.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal void OfferToTarget()
+ {
+ try
+ {
+ // Offer the message to the target. If there's no cancellation in play, we can just allow the target
+ // to accept the message directly. But if a CancellationToken is in use, the target needs to come
+ // back to us to get the data; that way, we can ensure we don't race between returning a canceled task but
+ // successfully completing the send.
+ bool consumeToAccept = _cancellationState != CANCELLATION_STATE_NONE;
+
+ switch (_target.OfferMessage(
+ Common.SingleMessageHeader, _messageValue, this, consumeToAccept: consumeToAccept))
+ {
+ // If the message is immediately accepted, complete the task as accepted
+ case DataflowMessageStatus.Accepted:
+ if (!consumeToAccept)
+ {
+ // Cancellation wasn't in use, and the target accepted the message directly,
+ // so complete the task as accepted.
+ CompleteAsAccepted(runAsync: false);
+ }
+ else
+ {
+ // If cancellation is in use, then since the target accepted,
+ // our state better reflect that we're completing.
+ Debug.Assert(_cancellationState == CANCELLATION_STATE_COMPLETING,
+ "The message was accepted, so we should have started completion.");
+ }
+ break;
+
+ // If the message is immediately declined, complete the task as declined
+ case DataflowMessageStatus.Declined:
+ case DataflowMessageStatus.DecliningPermanently:
+ CompleteAsDeclined(runAsync: false);
+ break;
+#if DEBUG
+ case DataflowMessageStatus.NotAvailable:
+ Debug.Assert(false, "The message should never be missed, as it's offered to only this one target");
+ break;
+ // If the message was postponed, the source may or may not be complete yet. Nothing to validate.
+ // Treat an improper DataflowMessageStatus as postponed and do nothing.
+#endif
+ }
+ }
+ // A faulty target might throw from OfferMessage. If that happens,
+ // we'll try to fault the returned task. A really faulty target might
+ // both throw from OfferMessage and call ConsumeMessage,
+ // in which case it's possible we might not be able to propagate the exception
+ // out to the caller through the task if ConsumeMessage wins the race,
+ // which is likely if the exception doesn't occur until after ConsumeMessage is
+ // called. If that happens, we just eat the exception.
+ catch (Exception exc)
+ {
+ Common.StoreDataflowMessageValueIntoExceptionData(exc, _messageValue);
+ CompleteAsFaulted(exc, runAsync: false);
+ }
+ }
+
+ /// <summary>Called by the target to consume the buffered message.</summary>
+ TOutput ISourceBlock<TOutput>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out Boolean messageConsumed)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // If the task has already completed, there's nothing to consume. This could happen if
+ // cancellation was already requested and completed the task as a result.
+ if (Task.IsCompleted)
+ {
+ messageConsumed = false;
+ return default(TOutput);
+ }
+
+ // If the message being asked for is not the same as the one that's buffered,
+ // something is wrong. Complete as having failed to transfer the message.
+ bool validMessage = (messageHeader.Id == Common.SINGLE_MESSAGE_ID);
+
+ if (validMessage)
+ {
+ int curState = _cancellationState;
+ Debug.Assert(
+ curState == CANCELLATION_STATE_NONE || curState == CANCELLATION_STATE_REGISTERED ||
+ curState == CANCELLATION_STATE_RESERVED || curState == CANCELLATION_STATE_COMPLETING,
+ "The current cancellation state is not valid.");
+
+ // If we're not dealing with cancellation, then if we're currently registered or reserved, try to transition
+ // to completing. If we're able to, allow the message to be consumed, and we're done. At this point, we
+ // support transitioning out of REGISTERED or RESERVED.
+ if (curState == CANCELLATION_STATE_NONE || // no synchronization necessary if there's no cancellation
+ (curState != CANCELLATION_STATE_COMPLETING && // fast check to avoid unnecessary synchronization
+ Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_COMPLETING, curState) == curState))
+ {
+ CompleteAsAccepted(runAsync: true);
+ messageConsumed = true;
+ return _messageValue;
+ }
+ }
+
+ // Consumption failed
+ messageConsumed = false;
+ return default(TOutput);
+ }
+
+ /// <summary>Called by the target to reserve the buffered message.</summary>
+ bool ISourceBlock<TOutput>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // If the task has already completed, such as due to cancellation, there's nothing to reserve.
+ if (Task.IsCompleted) return false;
+
+ // As long as the message is the one being requested and cancellation hasn't been requested, allow it to be reserved.
+ bool reservable = (messageHeader.Id == Common.SINGLE_MESSAGE_ID);
+ return reservable &&
+ (_cancellationState == CANCELLATION_STATE_NONE || // avoid synchronization when cancellation is not in play
+ Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_RESERVED, CANCELLATION_STATE_REGISTERED) == CANCELLATION_STATE_REGISTERED);
+ }
+
+ /// <summary>Called by the target to release a reservation on the buffered message.</summary>
+ void ISourceBlock<TOutput>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // If this is not the message we posted, bail
+ if (messageHeader.Id != Common.SINGLE_MESSAGE_ID)
+ throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+
+ // If the task has already completed, there's nothing to release.
+ if (Task.IsCompleted) return;
+
+ // If a cancellation token is being used, revert our state back to registered. In the meantime
+ // cancellation could have been requested, so check to see now if cancellation was requested
+ // and process it if it was.
+ if (_cancellationState != CANCELLATION_STATE_NONE)
+ {
+ if (Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_REGISTERED, CANCELLATION_STATE_RESERVED) != CANCELLATION_STATE_RESERVED)
+ throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+ if (_cancellationToken.IsCancellationRequested)
+ CancellationHandler(new WeakReference<SendAsyncSource<TOutput>>(this)); // same code as registered with the CancellationToken
+ }
+
+ // Start the process over by reoffering the message asynchronously.
+ OfferToTargetAsync();
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { return Task; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ IDisposable ISourceBlock<TOutput>.LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete() { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void IDataflowBlock.Fault(Exception exception) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayTarget = _target as IDebuggerDisplay;
+ return string.Format("{0} Message={1}, Target=\"{2}\"",
+ Common.GetNameForDebugger(this),
+ _messageValue,
+ displayTarget != null ? displayTarget.Content : _target);
+ }
+ }
+ /// <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 source.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The source.</summary>
+ private readonly SendAsyncSource<TOutput> _source;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="source">The source to view.</param>
+ public DebugView(SendAsyncSource<TOutput> source)
+ {
+ Contract.Requires(source != null, "Need a source with which to construct the debug view.");
+ _source = source;
+ }
+
+ /// <summary>The target to which we're linked.</summary>
+ public ITargetBlock<TOutput> Target { get { return _source._target; } }
+ /// <summary>The message buffered by the source.</summary>
+ public TOutput Message { get { return _source._messageValue; } }
+ /// <summary>The Task represented the posting of the message.</summary>
+ public Task<bool> Completion { get { return _source.Task; } }
+ }
+ }
+ #endregion
+
+ #region TryReceive, ReceiveAsync, and Receive
+ #region TryReceive
+ /// <summary>
+ /// Attempts to synchronously receive an item from the <see cref="T:System.Threading.Tasks.Dataflow.ISourceBlock`1"/>.
+ /// </summary>
+ /// <param name="source">The source from which to receive.</param>
+ /// <param name="item">The item received from the source.</param>
+ /// <returns>true if an item could be received; otherwise, false.</returns>
+ /// <remarks>
+ /// This method does not wait until the source has an item to provide.
+ /// It will return whether or not an element was available.
+ /// </remarks>
+ public static bool TryReceive<TOutput>(this IReceivableSourceBlock<TOutput> source, out TOutput item)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return source.TryReceive(null, out item);
+ }
+ #endregion
+
+ #region ReceiveAsync
+ /// <summary>Asynchronously receives a value from the specified source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to asynchronously receive.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
+ /// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
+ /// because the source is empty and completed, the returned task will be canceled.
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ public static Task<TOutput> ReceiveAsync<TOutput>(
+ this ISourceBlock<TOutput> source)
+ {
+ // Argument validation handled by target method
+ return ReceiveAsync(source, Common.InfiniteTimeSpan, CancellationToken.None);
+ }
+
+ /// <summary>Asynchronously receives a value from the specified source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to asynchronously receive.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
+ /// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
+ /// either because cancellation is requested or the source is empty and completed, the returned task will be canceled.
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ public static Task<TOutput> ReceiveAsync<TOutput>(
+ this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
+ {
+ // Argument validation handled by target method
+ return ReceiveAsync(source, Common.InfiniteTimeSpan, cancellationToken);
+ }
+
+ /// <summary>Asynchronously receives a value from the specified source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to asynchronously receive.</param>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
+ /// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
+ /// either because the timeout expires or the source is empty and completed, the returned task will be canceled.
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ public static Task<TOutput> ReceiveAsync<TOutput>(
+ this ISourceBlock<TOutput> source, TimeSpan timeout)
+ {
+ // Argument validation handled by target method
+ return ReceiveAsync(source, timeout, CancellationToken.None);
+ }
+
+ /// <summary>Asynchronously receives a value from the specified source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to asynchronously receive.</param>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
+ /// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
+ /// either because the timeout expires, cancellation is requested, or the source is empty and completed, the returned task will be canceled.
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ public static Task<TOutput> ReceiveAsync<TOutput>(
+ this ISourceBlock<TOutput> source, TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ // Validate arguments
+
+
+ if (source == null) throw new ArgumentNullException("source");
+ if (!Common.IsValidTimeout(timeout)) throw new ArgumentOutOfRangeException("timeout", SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+
+ // Return the task representing the core receive operation
+ return ReceiveCore(source, true, timeout, cancellationToken);
+ }
+ #endregion
+
+ #region Receive
+ /// <summary>Synchronously receives an item from the source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to receive.</param>
+ /// <returns>The received item.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
+ public static TOutput Receive<TOutput>(
+ this ISourceBlock<TOutput> source)
+ {
+ // Argument validation handled by target method
+ return Receive(source, Common.InfiniteTimeSpan, CancellationToken.None);
+ }
+
+ /// <summary>Synchronously receives an item from the source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to receive.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
+ /// <returns>The received item.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
+ /// <exception cref="System.OperationCanceledException">The operation was canceled before an item was received from the source.</exception>
+ /// <remarks>
+ /// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent cancellation request occurs.
+ /// </remarks>
+ public static TOutput Receive<TOutput>(
+ this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
+ {
+ // Argument validation handled by target method
+ return Receive(source, Common.InfiniteTimeSpan, cancellationToken);
+ }
+
+ /// <summary>Synchronously receives an item from the source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to receive.</param>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
+ /// <returns>The received item.</returns>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
+ /// <exception cref="System.TimeoutException">The specified timeout expired before an item was received from the source.</exception>
+ /// <remarks>
+ /// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent timeout occurs.
+ /// </remarks>
+ public static TOutput Receive<TOutput>(
+ this ISourceBlock<TOutput> source, TimeSpan timeout)
+ {
+ // Argument validation handled by target method
+ return Receive(source, timeout, CancellationToken.None);
+ }
+
+ /// <summary>Synchronously receives an item from the source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to receive.</param>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
+ /// <returns>The received item.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ /// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
+ /// <exception cref="System.TimeoutException">The specified timeout expired before an item was received from the source.</exception>
+ /// <exception cref="System.OperationCanceledException">The operation was canceled before an item was received from the source.</exception>
+ /// <remarks>
+ /// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent timeout or cancellation request occurs.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Usage", "CA2200:RethrowToPreserveStackDetails")]
+ public static TOutput Receive<TOutput>(
+ this ISourceBlock<TOutput> source, TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ // Validate arguments
+ if (source == null) throw new ArgumentNullException("source");
+ if (!Common.IsValidTimeout(timeout)) throw new ArgumentOutOfRangeException("timeout", SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+
+ // Do fast path checks for both cancellation and data already existing.
+ cancellationToken.ThrowIfCancellationRequested();
+ TOutput fastCheckedItem;
+ var receivableSource = source as IReceivableSourceBlock<TOutput>;
+ if (receivableSource != null && receivableSource.TryReceive(null, out fastCheckedItem))
+ {
+ return fastCheckedItem;
+ }
+
+ // Get a TCS to represent the receive operation and wait for it to complete.
+ // If it completes successfully, return the result. Otherwise, throw the
+ // original inner exception representing the cause. This could be an OCE.
+ Task<TOutput> task = ReceiveCore(source, false, timeout, cancellationToken);
+ try
+ {
+ return task.GetAwaiter().GetResult(); // block until the result is available
+ }
+ catch
+ {
+ // Special case cancellation in order to ensure the exception contains the token.
+ // The public TrySetCanceled, used by ReceiveCore, is parameterless and doesn't
+ // accept the token to use. Thus the exception that we're catching here
+ // won't contain the cancellation token we want propagated.
+ if (task.IsCanceled) cancellationToken.ThrowIfCancellationRequested();
+
+ // If we get here, propagate the original exception.
+ throw;
+ }
+ }
+ #endregion
+
+ #region Shared by Receive and ReceiveAsync
+ /// <summary>Receives an item from the source.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to receive.</param>
+ /// <param name="attemptTryReceive">Whether to first attempt using TryReceive to get a value from the source.</param>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
+ /// <returns>A Task for the receive operation.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static Task<TOutput> ReceiveCore<TOutput>(
+ this ISourceBlock<TOutput> source, bool attemptTryReceive, TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ Contract.Requires(source != null, "Need a source from which to receive.");
+
+ // If cancellation has been requested, we're done before we've even started, cancel this receive.
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Common.CreateTaskFromCancellation<TOutput>(cancellationToken);
+ }
+
+ if (attemptTryReceive)
+ {
+ // If we're able to directly and immediately receive an item, use that item to complete the receive.
+ var receivableSource = source as IReceivableSourceBlock<TOutput>;
+ if (receivableSource != null)
+ {
+ try
+ {
+ TOutput fastCheckedItem;
+ if (receivableSource.TryReceive(null, out fastCheckedItem))
+ {
+ return Task.FromResult<TOutput>(fastCheckedItem);
+ }
+ }
+ catch (Exception exc)
+ {
+ return Common.CreateTaskFromException<TOutput>(exc);
+ }
+ }
+ }
+
+ int millisecondsTimeout = (int)timeout.TotalMilliseconds;
+ if (millisecondsTimeout == 0)
+ {
+ return Common.CreateTaskFromException<TOutput>(ReceiveTarget<TOutput>.CreateExceptionForTimeout());
+ }
+
+ return ReceiveCoreByLinking<TOutput>(source, millisecondsTimeout, cancellationToken);
+ }
+
+ /// <summary>The reason for a ReceiveCoreByLinking call failing.</summary>
+ private enum ReceiveCoreByLinkingCleanupReason
+ {
+ /// <summary>The Receive operation completed successfully, obtaining a value from the source.</summary>
+ Success = 0,
+ /// <summary>The timer expired before a value could be received.</summary>
+ Timer = 1,
+ /// <summary>The cancellation token had cancellation requested before a value could be received.</summary>
+ Cancellation = 2,
+ /// <summary>The source completed before a value could be received.</summary>
+ SourceCompletion = 3,
+ /// <summary>An error occurred while linking up the target.</summary>
+ SourceProtocolError = 4,
+ /// <summary>An error during cleanup after completion for another reason.</summary>
+ ErrorDuringCleanup = 5
+ }
+
+ /// <summary>Cancels a CancellationTokenSource passed as the object state argument.</summary>
+ private static readonly Action<object> _cancelCts = state => ((CancellationTokenSource)state).Cancel();
+
+ /// <summary>Receives an item from the source by linking a temporary target from it.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source from which to receive.</param>
+ /// <param name="millisecondsTimeout">The number of milliseconds to wait, or -1 to wait indefinitely.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static Task<TOutput> ReceiveCoreByLinking<TOutput>(ISourceBlock<TOutput> source, int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ // Create a target to link from the source
+ var target = new ReceiveTarget<TOutput>();
+
+ // Keep cancellation registrations inside the try/catch in case the underlying CTS is disposed in which case an exception is thrown
+ try
+ {
+ // Create a cancellation token that will be canceled when either the provided token
+ // is canceled or the source block completes.
+ if (cancellationToken.CanBeCanceled)
+ {
+ target._externalCancellationToken = cancellationToken;
+ target._regFromExternalCancellationToken = cancellationToken.Register(_cancelCts, target._cts);
+ }
+
+ // We need to cleanup if one of a few things happens:
+ // - The target completes successfully due to receiving data.
+ // - The user-specified timeout occurs, such that we should bail on the receive.
+ // - The cancellation token has cancellation requested, such that we should bail on the receive.
+ // - The source completes, since it won't send any more data.
+ // Note that there's a potential race here, in that the cleanup delegate could be executed
+ // from the timer before the timer variable is set, but that's ok, because then timer variable
+ // will just show up as null in the cleanup and there will be nothing to dispose (nor will anything
+ // need to be disposed, since it's the timer that fired. Timer.Dispose is also thread-safe to be
+ // called multiple times concurrently.)
+ if (millisecondsTimeout > 0)
+ {
+ target._timer = new Timer(
+ ReceiveTarget<TOutput>.CachedLinkingTimerCallback, target,
+ millisecondsTimeout, Timeout.Infinite);
+ }
+
+ if (target._cts.Token.CanBeCanceled)
+ {
+ target._cts.Token.Register(
+ ReceiveTarget<TOutput>.CachedLinkingCancellationCallback, target); // we don't have to cleanup this registration, as this cts is short-lived
+ }
+
+ // Link the target to the source
+ IDisposable unlink = source.LinkTo(target, DataflowLinkOptions.UnlinkAfterOneAndPropagateCompletion);
+ target._unlink = unlink;
+
+ // If completion has started, there is a chance it started after we linked.
+ // In that case, we must dispose of the unlinker.
+ // If completion started before we linked, the cleanup code will try to unlink.
+ // So we are racing to dispose of the unlinker.
+ if (Volatile.Read(ref target._cleanupReserved))
+ {
+ IDisposable disposableUnlink = Interlocked.CompareExchange(ref target._unlink, null, unlink);
+ if (disposableUnlink != null) disposableUnlink.Dispose();
+ }
+ }
+ catch (Exception exception)
+ {
+ target._receivedException = exception;
+ target.TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.SourceProtocolError);
+ // If we lose the race here, we may end up eating this exception.
+ }
+
+ return target.Task;
+ }
+
+ /// <summary>Provides a TaskCompletionSource that is also a dataflow target for use in ReceiveCore.</summary>
+ /// <typeparam name="T">Specifies the type of data offered to the target.</typeparam>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ private sealed class ReceiveTarget<T> : TaskCompletionSource<T>, ITargetBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>Cached delegate used in ReceiveCoreByLinking on the created timer. Passed the ReceiveTarget as the argument.</summary>
+ /// <remarks>The C# compiler will not cache this delegate by default due to it being a generic method on a non-generic class.</remarks>
+ internal readonly static TimerCallback CachedLinkingTimerCallback = state =>
+ {
+ var receiveTarget = (ReceiveTarget<T>)state;
+ receiveTarget.TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.Timer);
+ };
+
+ /// <summary>Cached delegate used in ReceiveCoreByLinking on the cancellation token. Passed the ReceiveTarget as the state argument.</summary>
+ /// <remarks>The C# compiler will not cache this delegate by default due to it being a generic method on a non-generic class.</remarks>
+ internal readonly static Action<object> CachedLinkingCancellationCallback = state =>
+ {
+ var receiveTarget = (ReceiveTarget<T>)state;
+ receiveTarget.TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.Cancellation);
+ };
+
+ /// <summary>The received value if we accepted a value from the source.</summary>
+ private T _receivedValue;
+
+ /// <summary>The cancellation token source representing both external and internal cancellation.</summary>
+ internal readonly CancellationTokenSource _cts = new CancellationTokenSource();
+ /// <summary>Indicates a code path is already on route to complete the target. 0 is false, 1 is true.</summary>
+ internal bool _cleanupReserved; // must only be accessed under IncomingLock
+ /// <summary>The external token that cancels the internal token.</summary>
+ internal CancellationToken _externalCancellationToken;
+ /// <summary>The registration on the external token that cancels the internal token.</summary>
+ internal CancellationTokenRegistration _regFromExternalCancellationToken;
+ /// <summary>The timer that fires when the timeout has been exceeded.</summary>
+ internal Timer _timer;
+ /// <summary>The unlinker from removing this target from the source from which we're receiving.</summary>
+ internal IDisposable _unlink;
+ /// <summary>The received exception if an error occurred.</summary>
+ internal Exception _receivedException;
+
+ /// <summary>Gets the sync obj used to synchronize all activity on this target.</summary>
+ internal object IncomingLock { get { return _cts; } }
+
+ /// <summary>Initializes the target.</summary>
+ internal ReceiveTarget() { }
+
+ /// <summary>Offers a message to be used to complete the TaskCompletionSource.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ 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();
+
+ DataflowMessageStatus status = DataflowMessageStatus.NotAvailable;
+
+ // If we're already one our way to being done, don't accept anything.
+ // This is a fast-path check prior to taking the incoming lock;
+ // _cleanupReserved only ever goes from false to true.
+ if (Volatile.Read(ref _cleanupReserved)) return DataflowMessageStatus.DecliningPermanently;
+
+ lock (IncomingLock)
+ {
+ // Check again now that we've taken the lock
+ if (_cleanupReserved) return DataflowMessageStatus.DecliningPermanently;
+
+ try
+ {
+ // Accept the message if possible and complete this task with the message's value.
+ bool consumed = true;
+ T acceptedValue = consumeToAccept ? source.ConsumeMessage(messageHeader, this, out consumed) : messageValue;
+ if (consumed)
+ {
+ status = DataflowMessageStatus.Accepted;
+ _receivedValue = acceptedValue;
+ _cleanupReserved = true;
+ }
+ }
+ catch (Exception exc)
+ {
+ // An error occurred. Take ourselves out of the game.
+ status = DataflowMessageStatus.DecliningPermanently;
+ Common.StoreDataflowMessageValueIntoExceptionData(exc, messageValue);
+ _receivedException = exc;
+ _cleanupReserved = true;
+ }
+ }
+
+ // Do any cleanup outside of the lock. The right to cleanup was reserved above for these cases.
+ if (status == DataflowMessageStatus.Accepted)
+ {
+ CleanupAndComplete(ReceiveCoreByLinkingCleanupReason.Success);
+ }
+ else if (status == DataflowMessageStatus.DecliningPermanently) // should only be the case if an error occurred
+ {
+ CleanupAndComplete(ReceiveCoreByLinkingCleanupReason.SourceProtocolError);
+ }
+
+ return status;
+ }
+
+ /// <summary>
+ /// Attempts to reserve the right to cleanup and complete, and if successfully,
+ /// continues to cleanup and complete.
+ /// </summary>
+ /// <param name="reason">The reason we're completing and cleaning up.</param>
+ /// <returns>true if successful in completing; otherwise, false.</returns>
+ internal bool TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason reason)
+ {
+ // If cleanup was already reserved, bail.
+ if (Volatile.Read(ref _cleanupReserved)) return false;
+
+ // Atomically using IncomingLock try to reserve the completion routine.
+ lock (IncomingLock)
+ {
+ if (_cleanupReserved) return false;
+ _cleanupReserved = true;
+ }
+
+ // We've reserved cleanup and completion, so do it.
+ CleanupAndComplete(reason);
+ return true;
+ }
+
+ /// <summary>Cleans up the target for completion.</summary>
+ /// <param name="reason">The reason we're completing and cleaning up.</param>
+ /// <remarks>This method must only be called once on this instance.</remarks>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
+ private void CleanupAndComplete(ReceiveCoreByLinkingCleanupReason reason)
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+ Debug.Assert(Volatile.Read(ref _cleanupReserved), "Should only be called once by whomever reserved the right.");
+
+ // Unlink from the source. If we're cleaning up because the source
+ // completed, this is unnecessary, as the source should have already
+ // emptied out its target registry, or at least be in the process of doing so.
+ // We are racing with the linking code - only one can dispose of the unlinker.
+ IDisposable unlink = _unlink;
+ if (reason != ReceiveCoreByLinkingCleanupReason.SourceCompletion && unlink != null)
+ {
+ IDisposable disposableUnlink = Interlocked.CompareExchange(ref _unlink, null, unlink);
+ if (disposableUnlink != null)
+ {
+ // If an error occurs, fault the target and override the reason to
+ // continue executing, i.e. do the remaining cleanup without completing
+ // the target the way we originally intended to.
+ try
+ {
+ disposableUnlink.Dispose(); // must not be holding IncomingLock, or could deadlock
+ }
+ catch (Exception exc)
+ {
+ _receivedException = exc;
+ reason = ReceiveCoreByLinkingCleanupReason.SourceProtocolError;
+ }
+ }
+ }
+
+ // Cleanup the timer. (Even if we're here because of the timer firing, we still
+ // want to aggressively dispose of the timer.)
+ if (_timer != null) _timer.Dispose();
+
+ // Cancel the token everyone is listening to. We also want to unlink
+ // from the user-provided cancellation token to prevent a leak.
+ // We do *not* dispose of the cts itself here, as there could be a race
+ // with the code registering this cleanup delegate with cts; not disposing
+ // is ok, though, because there's no resources created by the CTS
+ // that needs to be cleaned up since we're not using the wait handle.
+ // This is also why we don't use CreateLinkedTokenSource, as that combines
+ // both disposing of the token source and disposal of the connection link
+ // into a single dispose operation.
+ // if we're here because of cancellation, no need to cancel again
+ if (reason != ReceiveCoreByLinkingCleanupReason.Cancellation)
+ {
+ // if the source complete without receiving a value, we check the cancellation one more time
+ if (reason == ReceiveCoreByLinkingCleanupReason.SourceCompletion &&
+ (_externalCancellationToken.IsCancellationRequested || _cts.IsCancellationRequested))
+ {
+ reason = ReceiveCoreByLinkingCleanupReason.Cancellation;
+ }
+ _cts.Cancel();
+ }
+ _regFromExternalCancellationToken.Dispose();
+
+ // No need to dispose of the cts, either, as we're not accessing its WaitHandle
+ // nor was it created as a linked token source. Disposing it could also be dangerous
+ // if other code tries to access it after we dispose of it... best to leave it available.
+
+ // Complete the task based on the reason
+ switch (reason)
+ {
+ // Task final state: RanToCompletion
+ case ReceiveCoreByLinkingCleanupReason.Success:
+ System.Threading.Tasks.Task.Factory.StartNew(state =>
+ {
+ // Complete with the received value
+ var target = (ReceiveTarget<T>)state;
+ try { target.TrySetResult(target._receivedValue); }
+ catch (ObjectDisposedException) { /* benign race if returned task is already disposed */ }
+ }, this, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
+ break;
+
+ // Task final state: Canceled
+ case ReceiveCoreByLinkingCleanupReason.Cancellation:
+ System.Threading.Tasks.Task.Factory.StartNew(state =>
+ {
+ // Complete as canceled
+ var target = (ReceiveTarget<T>)state;
+ try { target.TrySetCanceled(); }
+ catch (ObjectDisposedException) { /* benign race if returned task is already disposed */ }
+ }, this, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
+ break;
+ default:
+ Debug.Assert(false, "Invalid linking cleanup reason specified.");
+ goto case ReceiveCoreByLinkingCleanupReason.Cancellation;
+
+ // Task final state: Faulted
+ case ReceiveCoreByLinkingCleanupReason.SourceCompletion:
+ if (_receivedException == null) _receivedException = CreateExceptionForSourceCompletion();
+ goto case ReceiveCoreByLinkingCleanupReason.SourceProtocolError;
+ case ReceiveCoreByLinkingCleanupReason.Timer:
+ if (_receivedException == null) _receivedException = CreateExceptionForTimeout();
+ goto case ReceiveCoreByLinkingCleanupReason.SourceProtocolError;
+ case ReceiveCoreByLinkingCleanupReason.SourceProtocolError:
+ case ReceiveCoreByLinkingCleanupReason.ErrorDuringCleanup:
+ Debug.Assert(_receivedException != null, "We need an exception with which to fault the task.");
+ System.Threading.Tasks.Task.Factory.StartNew(state =>
+ {
+ // Complete with the received exception
+ var target = (ReceiveTarget<T>)state;
+ try { target.TrySetException(target._receivedException ?? new Exception()); }
+ catch (ObjectDisposedException) { /* benign race if returned task is already disposed */ }
+ }, this, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
+ break;
+ }
+ }
+
+ /// <summary>Creates an exception to use when a source completed before receiving a value.</summary>
+ /// <returns>The initialized exception.</returns>
+ internal static Exception CreateExceptionForSourceCompletion()
+ {
+ return Common.InitializeStackTrace(new InvalidOperationException(SR.InvalidOperation_DataNotAvailableForReceive));
+ }
+
+ /// <summary>Creates an exception to use when a timeout occurs before receiving a value.</summary>
+ /// <returns>The initialized exception.</returns>
+ internal static Exception CreateExceptionForTimeout()
+ {
+ return Common.InitializeStackTrace(new TimeoutException());
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete()
+ {
+ TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.SourceCompletion);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void IDataflowBlock.Fault(Exception exception) { ((IDataflowBlock)this).Complete(); }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ return string.Format("{0} IsCompleted={1}",
+ Common.GetNameForDebugger(this), base.Task.IsCompleted);
+ }
+ }
+ /// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
+ object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
+ }
+ #endregion
+ #endregion
+
+ #region OutputAvailableAsync
+ /// <summary>
+ /// Provides a <see cref="System.Threading.Tasks.Task{TResult}"/>
+ /// that asynchronously monitors the source for available output.
+ /// </summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source to monitor.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{Boolean}"/> that informs of whether and when
+ /// more output is available. When the task completes, if its <see cref="System.Threading.Tasks.Task{Boolean}.Result"/> is true, more output
+ /// is available in the source (though another consumer of the source may retrieve the data).
+ /// If it returns false, more output is not and will never be available, due to the source
+ /// completing prior to output being available.
+ /// </returns>
+ public static Task<bool> OutputAvailableAsync<TOutput>(this ISourceBlock<TOutput> source)
+ {
+ return OutputAvailableAsync<TOutput>(source, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Provides a <see cref="System.Threading.Tasks.Task{TResult}"/>
+ /// that asynchronously monitors the source for available output.
+ /// </summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source to monitor.</param>
+ /// <param name="cancellationToken">The cancellation token with which to cancel the asynchronous operation.</param>
+ /// <returns>
+ /// A <see cref="System.Threading.Tasks.Task{Boolean}"/> that informs of whether and when
+ /// more output is available. When the task completes, if its <see cref="System.Threading.Tasks.Task{Boolean}.Result"/> is true, more output
+ /// is available in the source (though another consumer of the source may retrieve the data).
+ /// If it returns false, more output is not and will never be available, due to the source
+ /// completing prior to output being available.
+ /// </returns>
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+ public static Task<bool> OutputAvailableAsync<TOutput>(
+ this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
+ {
+ // Validate arguments
+ if (source == null) throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Fast path for cancellation
+ if (cancellationToken.IsCancellationRequested)
+ return Common.CreateTaskFromCancellation<bool>(cancellationToken);
+
+ // In a method like this, normally we would want to check source.Completion.IsCompleted
+ // and avoid linking completely by simply returning a completed task. However,
+ // some blocks that are completed still have data available, like WriteOnceBlock,
+ // which completes as soon as it gets a value and stores that value forever.
+ // As such, OutputAvailableAsync must link from the source so that the source
+ // can push data to us if it has it, at which point we can immediately unlink.
+
+ // Create a target task that will complete when it's offered a message (but it won't accept the message)
+ var target = new OutputAvailableAsyncTarget<TOutput>();
+ try
+ {
+ // Link from the source. If the source propagates a message during or immediately after linking
+ // such that our target is already completed, just return its task.
+ target._unlinker = source.LinkTo(target, DataflowLinkOptions.UnlinkAfterOneAndPropagateCompletion);
+
+ // If the task is already completed (an exception may have occurred, or the source may have propagated
+ // a message to the target during LinkTo or soon thereafter), just return the task directly.
+ if (target.Task.IsCompleted)
+ {
+ return target.Task;
+ }
+
+ // If cancellation could be requested, hook everything up to be notified of cancellation requests.
+ if (cancellationToken.CanBeCanceled)
+ {
+ // When cancellation is requested, unlink the target from the source and cancel the target.
+ target._ctr = cancellationToken.Register(OutputAvailableAsyncTarget<TOutput>.s_cancelAndUnlink, target);
+ }
+
+ // We can't return the task directly, as the source block will be completing the task synchronously,
+ // and thus any synchronous continuations would run as part of the source block's call. We don't have to worry
+ // about cancellation, as we've coded cancellation to complete the task asynchronously, and with the continuation
+ // set as NotOnCanceled, so the continuation will be canceled immediately when the antecedent is canceled, which
+ // will thus be asynchronously from the cancellation token source's cancellation call.
+ return target.Task.ContinueWith(
+ OutputAvailableAsyncTarget<TOutput>.s_handleCompletion, target,
+ CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
+ }
+ catch (Exception exc)
+ {
+ // Source.LinkTo could throw, as could cancellationToken.Register if cancellation was already requested
+ // such that it synchronously invokes the source's unlinker IDisposable, which could throw.
+ target.TrySetException(exc);
+
+ // Undo the link from the source to the target
+ target.AttemptThreadSafeUnlink();
+
+ // Return the now faulted task
+ return target.Task;
+ }
+ }
+
+ /// <summary>Provides a target used in OutputAvailableAsync operations.</summary>
+ /// <typeparam name="T">Specifies the type of data in the data source being checked.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ private sealed class OutputAvailableAsyncTarget<T> : TaskCompletionSource<bool>, ITargetBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>
+ /// Cached continuation delegate that unregisters from cancellation and
+ /// marshals the antecedent's result to the return value.
+ /// </summary>
+ internal readonly static Func<Task<bool>, object, bool> s_handleCompletion = (antecedent, state) =>
+ {
+ var target = state as OutputAvailableAsyncTarget<T>;
+ Debug.Assert(target != null, "Expected non-null target");
+ target._ctr.Dispose();
+ return antecedent.GetAwaiter().GetResult();
+ };
+
+ /// <summary>
+ /// Cached delegate that cancels the target and unlinks the target from the source.
+ /// Expects an OutputAvailableAsyncTarget as the state argument.
+ /// </summary>
+ internal readonly static Action<object> s_cancelAndUnlink = CancelAndUnlink;
+
+ /// <summary>Cancels the target and unlinks the target from the source.</summary>
+ /// <param name="state">An OutputAvailableAsyncTarget.</param>
+ private static void CancelAndUnlink(object state)
+ {
+ var target = state as OutputAvailableAsyncTarget<T>;
+ Debug.Assert(target != null, "Expected a non-null target");
+
+ // Cancel asynchronously so that we're not completing the task as part of the cts.Cancel() call,
+ // since synchronous continuations off that task would then run as part of Cancel.
+ // Take advantage of this task and unlink from there to avoid doing the interlocked operation synchronously.
+ System.Threading.Tasks.Task.Factory.StartNew(tgt =>
+ {
+ var thisTarget = (OutputAvailableAsyncTarget<T>)tgt;
+ thisTarget.TrySetCanceled();
+ thisTarget.AttemptThreadSafeUnlink();
+ },
+ target, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+
+ /// <summary>Disposes of _unlinker if the target has been linked.</summary>
+ internal void AttemptThreadSafeUnlink()
+ {
+ // A race is possible. Therefore use an interlocked operation.
+ IDisposable cachedUnlinker = _unlinker;
+ if (cachedUnlinker != null && Interlocked.CompareExchange(ref _unlinker, null, cachedUnlinker) == cachedUnlinker)
+ {
+ cachedUnlinker.Dispose();
+ }
+ }
+
+ /// <summary>The IDisposable used to unlink this target from its source.</summary>
+ internal IDisposable _unlinker;
+ /// <summary>The registration used to unregister this target from the cancellation token.</summary>
+ internal CancellationTokenRegistration _ctr;
+
+ /// <summary>Completes the task when offered a message (but doesn't consume the message).</summary>
+ DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source, Boolean consumeToAccept)
+ {
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (source == null) throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ TrySetResult(true);
+ return DataflowMessageStatus.DecliningPermanently;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete()
+ {
+ TrySetResult(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();
+ TrySetResult(false);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ return string.Format("{0} IsCompleted={1}",
+ Common.GetNameForDebugger(this), base.Task.IsCompleted);
+ }
+ }
+ /// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
+ object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
+ }
+ #endregion
+
+ #region Encapsulate
+ /// <summary>Encapsulates a target and a source into a single propagator.</summary>
+ /// <typeparam name="TInput">Specifies the type of input expected by the target.</typeparam>
+ /// <typeparam name="TOutput">Specifies the type of output produced by the source.</typeparam>
+ /// <param name="target">The target to encapsulate.</param>
+ /// <param name="source">The source to encapsulate.</param>
+ /// <returns>The encapsulated target and source.</returns>
+ /// <remarks>
+ /// This method does not in any way connect the target to the source. It creates a
+ /// propagator block whose target methods delegate to the specified target and whose
+ /// source methods delegate to the specified source. Any connection between the target
+ /// and the source is left for the developer to explicitly provide. The propagator's
+ /// <see cref="IDataflowBlock"/> implementation delegates to the specified source.
+ /// </remarks>
+ public static IPropagatorBlock<TInput, TOutput> Encapsulate<TInput, TOutput>(
+ ITargetBlock<TInput> target, ISourceBlock<TOutput> source)
+ {
+ if (target == null) throw new ArgumentNullException("target");
+ if (source == null) throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+ return new EncapsulatingPropagator<TInput, TOutput>(target, source);
+ }
+
+ /// <summary>Provides a dataflow block that encapsulates a target and a source to form a single propagator.</summary>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(EncapsulatingPropagator<,>.DebugView))]
+ private sealed class EncapsulatingPropagator<TInput, TOutput> : IPropagatorBlock<TInput, TOutput>, IReceivableSourceBlock<TOutput>, IDebuggerDisplay
+ {
+ /// <summary>The target half.</summary>
+ private ITargetBlock<TInput> _target;
+ /// <summary>The source half.</summary>
+ private ISourceBlock<TOutput> _source;
+
+ public EncapsulatingPropagator(ITargetBlock<TInput> target, ISourceBlock<TOutput> source)
+ {
+ Contract.Requires(target != null, "The target should never be null; this should be checked by all internal usage.");
+ Contract.Requires(source != null, "The source should never be null; this should be checked by all internal usage.");
+ _target = target;
+ _source = source;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ public void Complete()
+ {
+ _target.Complete();
+ }
+
+ /// <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.Fault(exception);
+ }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, bool consumeToAccept)
+ {
+ return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept);
+ }
+
+ /// <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="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 bool TryReceive(Predicate<TOutput> filter, out TOutput item)
+ {
+ var receivableSource = _source as IReceivableSourceBlock<TOutput>;
+ if (receivableSource != null) return receivableSource.TryReceive(filter, out item);
+
+ item = default(TOutput);
+ return false;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ public bool TryReceiveAll(out IList<TOutput> items)
+ {
+ var receivableSource = _source as IReceivableSourceBlock<TOutput>;
+ if (receivableSource != null) return receivableSource.TryReceiveAll(out items);
+
+ items = default(IList<TOutput>);
+ return false;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ public 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"]/*' />
+ public bool ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ return _source.ReserveMessage(messageHeader, target);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ public void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ _source.ReleaseReservation(messageHeader, target);
+ }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayTarget = _target as IDebuggerDisplay;
+ var displaySource = _source as IDebuggerDisplay;
+ return string.Format("{0} Target=\"{1}\", Source=\"{2}\"",
+ Common.GetNameForDebugger(this),
+ displayTarget != null ? displayTarget.Content : _target,
+ displaySource != null ? displaySource.Content : _source);
+ }
+ }
+ /// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
+ object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
+
+ /// <summary>A debug view for the propagator.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The propagator being debugged.</summary>
+ private readonly EncapsulatingPropagator<TInput, TOutput> _propagator;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="propagator">The propagator being debugged.</param>
+ public DebugView(EncapsulatingPropagator<TInput, TOutput> propagator)
+ {
+ Contract.Requires(propagator != null, "Need a block with which to construct the debug view.");
+ _propagator = propagator;
+ }
+
+ /// <summary>The target.</summary>
+ public ITargetBlock<TInput> Target { get { return _propagator._target; } }
+ /// <summary>The source.</summary>
+ public ISourceBlock<TOutput> Source { get { return _propagator._source; } }
+ }
+ }
+ #endregion
+
+ #region Choose
+ #region Choose<T1,T2>
+ /// <summary>Monitors two dataflow sources, invoking the provided handler for whichever source makes data available first.</summary>
+ /// <typeparam name="T1">Specifies type of data contained in the first source.</typeparam>
+ /// <typeparam name="T2">Specifies type of data contained in the second source.</typeparam>
+ /// <param name="source1">The first source.</param>
+ /// <param name="action1">The handler to execute on data from the first source.</param>
+ /// <param name="source2">The second source.</param>
+ /// <param name="action2">The handler to execute on data from the second source.</param>
+ /// <returns>
+ /// <para>
+ /// A <see cref="System.Threading.Tasks.Task{Int32}"/> that represents the asynchronous choice.
+ /// If both sources are completed prior to the choice completing,
+ /// the resulting task will be canceled. When one of the sources has data available and successfully propagates
+ /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception,
+ /// the task will end in the <see cref="System.Threading.Tasks.TaskStatus.Faulted"/> state containing the unhandled exception, otherwise the task
+ /// will end with its <see cref="System.Threading.Tasks.Task{Int32}.Result"/> set to either 0 or 1 to
+ /// represent the first or second source, respectively.
+ /// </para>
+ /// <para>
+ /// This method will only consume an element from one of the two data sources, never both.
+ /// </para>
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action2"/> is null (Nothing in Visual Basic).</exception>
+ public static Task<Int32> Choose<T1, T2>(
+ ISourceBlock<T1> source1, Action<T1> action1,
+ ISourceBlock<T2> source2, Action<T2> action2)
+ {
+ // All argument validation is handled by the delegated method
+ return Choose(source1, action1, source2, action2, DataflowBlockOptions.Default);
+ }
+
+ /// <summary>Monitors two dataflow sources, invoking the provided handler for whichever source makes data available first.</summary>
+ /// <typeparam name="T1">Specifies type of data contained in the first source.</typeparam>
+ /// <typeparam name="T2">Specifies type of data contained in the second source.</typeparam>
+ /// <param name="source1">The first source.</param>
+ /// <param name="action1">The handler to execute on data from the first source.</param>
+ /// <param name="source2">The second source.</param>
+ /// <param name="action2">The handler to execute on data from the second source.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this choice.</param>
+ /// <returns>
+ /// <para>
+ /// A <see cref="System.Threading.Tasks.Task{Int32}"/> that represents the asynchronous choice.
+ /// If both sources are completed prior to the choice completing, or if the CancellationToken
+ /// provided as part of <paramref name="dataflowBlockOptions"/> is canceled prior to the choice completing,
+ /// the resulting task will be canceled. When one of the sources has data available and successfully propagates
+ /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception,
+ /// the task will end in the <see cref="System.Threading.Tasks.TaskStatus.Faulted"/> state containing the unhandled exception, otherwise the task
+ /// will end with its <see cref="System.Threading.Tasks.Task{Int32}.Result"/> set to either 0 or 1 to
+ /// represent the first or second source, respectively.
+ /// </para>
+ /// <para>
+ /// This method will only consume an element from one of the two data sources, never both.
+ /// If cancellation is requested after an element has been received, the cancellation request will be ignored,
+ /// and the relevant handler will be allowed to execute.
+ /// </para>
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+ public static Task<Int32> Choose<T1, T2>(
+ ISourceBlock<T1> source1, Action<T1> action1,
+ ISourceBlock<T2> source2, Action<T2> action2,
+ DataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments
+ if (source1 == null) throw new ArgumentNullException("source1");
+ if (action1 == null) throw new ArgumentNullException("action1");
+ if (source2 == null) throw new ArgumentNullException("source2");
+ if (action2 == null) throw new ArgumentNullException("action2");
+ if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions");
+
+ // Delegate to the shared implementation
+ return ChooseCore<T1, T2, VoidResult>(source1, action1, source2, action2, null, null, dataflowBlockOptions);
+ }
+ #endregion
+
+ #region Choose<T1,T2,T3>
+ /// <summary>Monitors three dataflow sources, invoking the provided handler for whichever source makes data available first.</summary>
+ /// <typeparam name="T1">Specifies type of data contained in the first source.</typeparam>
+ /// <typeparam name="T2">Specifies type of data contained in the second source.</typeparam>
+ /// <typeparam name="T3">Specifies type of data contained in the third source.</typeparam>
+ /// <param name="source1">The first source.</param>
+ /// <param name="action1">The handler to execute on data from the first source.</param>
+ /// <param name="source2">The second source.</param>
+ /// <param name="action2">The handler to execute on data from the second source.</param>
+ /// <param name="source3">The third source.</param>
+ /// <param name="action3">The handler to execute on data from the third source.</param>
+ /// <returns>
+ /// <para>
+ /// A <see cref="System.Threading.Tasks.Task{Int32}"/> that represents the asynchronous choice.
+ /// If all sources are completed prior to the choice completing,
+ /// the resulting task will be canceled. When one of the sources has data available and successfully propagates
+ /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception,
+ /// the task will end in the <see cref="System.Threading.Tasks.TaskStatus.Faulted"/> state containing the unhandled exception, otherwise the task
+ /// will end with its <see cref="System.Threading.Tasks.Task{Int32}.Result"/> set to the 0-based index of the source.
+ /// </para>
+ /// <para>
+ /// This method will only consume an element from one of the data sources, never more than one.
+ /// </para>
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source3"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action3"/> is null (Nothing in Visual Basic).</exception>
+ public static Task<Int32> Choose<T1, T2, T3>(
+ ISourceBlock<T1> source1, Action<T1> action1,
+ ISourceBlock<T2> source2, Action<T2> action2,
+ ISourceBlock<T3> source3, Action<T3> action3)
+ {
+ // All argument validation is handled by the delegated method
+ return Choose(source1, action1, source2, action2, source3, action3, DataflowBlockOptions.Default);
+ }
+
+ /// <summary>Monitors three dataflow sources, invoking the provided handler for whichever source makes data available first.</summary>
+ /// <typeparam name="T1">Specifies type of data contained in the first source.</typeparam>
+ /// <typeparam name="T2">Specifies type of data contained in the second source.</typeparam>
+ /// <typeparam name="T3">Specifies type of data contained in the third source.</typeparam>
+ /// <param name="source1">The first source.</param>
+ /// <param name="action1">The handler to execute on data from the first source.</param>
+ /// <param name="source2">The second source.</param>
+ /// <param name="action2">The handler to execute on data from the second source.</param>
+ /// <param name="source3">The third source.</param>
+ /// <param name="action3">The handler to execute on data from the third source.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this choice.</param>
+ /// <returns>
+ /// <para>
+ /// A <see cref="System.Threading.Tasks.Task{Int32}"/> that represents the asynchronous choice.
+ /// If all sources are completed prior to the choice completing, or if the CancellationToken
+ /// provided as part of <paramref name="dataflowBlockOptions"/> is canceled prior to the choice completing,
+ /// the resulting task will be canceled. When one of the sources has data available and successfully propagates
+ /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception,
+ /// the task will end in the <see cref="System.Threading.Tasks.TaskStatus.Faulted"/> state containing the unhandled exception, otherwise the task
+ /// will end with its <see cref="System.Threading.Tasks.Task{Int32}.Result"/> set to the 0-based index of the source.
+ /// </para>
+ /// <para>
+ /// This method will only consume an element from one of the data sources, never more than one.
+ /// If cancellation is requested after an element has been received, the cancellation request will be ignored,
+ /// and the relevant handler will be allowed to execute.
+ /// </para>
+ /// </returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action1"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action2"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source3"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action3"/> is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+ public static Task<Int32> Choose<T1, T2, T3>(
+ ISourceBlock<T1> source1, Action<T1> action1,
+ ISourceBlock<T2> source2, Action<T2> action2,
+ ISourceBlock<T3> source3, Action<T3> action3,
+ DataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments
+ if (source1 == null) throw new ArgumentNullException("source1");
+ if (action1 == null) throw new ArgumentNullException("action1");
+ if (source2 == null) throw new ArgumentNullException("source2");
+ if (action2 == null) throw new ArgumentNullException("action2");
+ if (source3 == null) throw new ArgumentNullException("source3");
+ if (action3 == null) throw new ArgumentNullException("action3");
+ if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions");
+
+ // Delegate to the shared implementation
+ return ChooseCore<T1, T2, T3>(source1, action1, source2, action2, source3, action3, dataflowBlockOptions);
+ }
+ #endregion
+
+ #region Choose Shared
+ /// <summary>Monitors dataflow sources, invoking the provided handler for whichever source makes data available first.</summary>
+ /// <typeparam name="T1">Specifies type of data contained in the first source.</typeparam>
+ /// <typeparam name="T2">Specifies type of data contained in the second source.</typeparam>
+ /// <typeparam name="T3">Specifies type of data contained in the third source.</typeparam>
+ /// <param name="source1">The first source.</param>
+ /// <param name="action1">The handler to execute on data from the first source.</param>
+ /// <param name="source2">The second source.</param>
+ /// <param name="action2">The handler to execute on data from the second source.</param>
+ /// <param name="source3">The third source.</param>
+ /// <param name="action3">The handler to execute on data from the third source.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this choice.</param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+ private static Task<Int32> ChooseCore<T1, T2, T3>(
+ ISourceBlock<T1> source1, Action<T1> action1,
+ ISourceBlock<T2> source2, Action<T2> action2,
+ ISourceBlock<T3> source3, Action<T3> action3,
+ DataflowBlockOptions dataflowBlockOptions)
+ {
+ Contract.Requires(source1 != null && action1 != null, "The first source and action should not be null.");
+ Contract.Requires(source2 != null && action2 != null, "The second source and action should not be null.");
+ Contract.Requires((source3 == null) == (action3 == null), "The third action should be null iff the third source is null.");
+ Contract.Requires(dataflowBlockOptions != null, "Options are required.");
+ bool hasThirdSource = source3 != null; // In the future, if we want higher arities on Choose, we can simply add more such checks on additional arguments
+
+ // Early cancellation check and bail out
+ if (dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ return Common.CreateTaskFromCancellation<Int32>(dataflowBlockOptions.CancellationToken);
+
+ // Fast path: if any of the sources already has data available that can be received immediately.
+ Task<int> resultTask;
+ try
+ {
+ TaskScheduler scheduler = dataflowBlockOptions.TaskScheduler;
+ if (TryChooseFromSource(source1, action1, 0, scheduler, out resultTask) ||
+ TryChooseFromSource(source2, action2, 1, scheduler, out resultTask) ||
+ (hasThirdSource && TryChooseFromSource(source3, action3, 2, scheduler, out resultTask)))
+ {
+ return resultTask;
+ }
+ }
+ catch (Exception exc)
+ {
+ // In case TryReceive in TryChooseFromSource erroneously throws
+ return Common.CreateTaskFromException<int>(exc);
+ }
+
+ // Slow path: link up to all of the sources. Separated out to avoid a closure on the fast path.
+ return ChooseCoreByLinking(source1, action1, source2, action2, source3, action3, dataflowBlockOptions);
+ }
+
+ /// <summary>
+ /// Tries to remove data from a receivable source and schedule an action to process that received item.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of data to process.</typeparam>
+ /// <param name="source">The source from which to receive the data.</param>
+ /// <param name="action">The action to run for the received data.</param>
+ /// <param name="branchId">The branch ID associated with this source/action pair.</param>
+ /// <param name="scheduler">The scheduler to use to process the action.</param>
+ /// <param name="task">The task created for processing the received item.</param>
+ /// <returns>true if this try attempt satisfies the choose operation; otherwise, false.</returns>
+ private static bool TryChooseFromSource<T>(
+ ISourceBlock<T> source, Action<T> action, int branchId, TaskScheduler scheduler,
+ out Task<int> task)
+ {
+ // Validate arguments
+ Contract.Requires(source != null, "Expected a non-null source");
+ Contract.Requires(action != null, "Expected a non-null action");
+ Contract.Requires(branchId >= 0, "Expected a valid branch ID (> 0)");
+ Contract.Requires(scheduler != null, "Expected a non-null scheduler");
+
+ // Try to receive from the source. If we can't, bail.
+ T result;
+ var receivableSource = source as IReceivableSourceBlock<T>;
+ if (receivableSource == null || !receivableSource.TryReceive(out result))
+ {
+ task = null;
+ return false;
+ }
+
+ // We successfully received an item. Launch a task to process it.
+ task = Task.Factory.StartNew(ChooseTarget<T>.s_processBranchFunction,
+ Tuple.Create<Action<T>, T, int>(action, result, branchId),
+ CancellationToken.None, Common.GetCreationOptionsForTask(), scheduler);
+ return true;
+ }
+
+ /// <summary>Monitors dataflow sources, invoking the provided handler for whichever source makes data available first.</summary>
+ /// <typeparam name="T1">Specifies type of data contained in the first source.</typeparam>
+ /// <typeparam name="T2">Specifies type of data contained in the second source.</typeparam>
+ /// <typeparam name="T3">Specifies type of data contained in the third source.</typeparam>
+ /// <param name="source1">The first source.</param>
+ /// <param name="action1">The handler to execute on data from the first source.</param>
+ /// <param name="source2">The second source.</param>
+ /// <param name="action2">The handler to execute on data from the second source.</param>
+ /// <param name="source3">The third source.</param>
+ /// <param name="action3">The handler to execute on data from the third source.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this choice.</param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+ private static Task<Int32> ChooseCoreByLinking<T1, T2, T3>(
+ ISourceBlock<T1> source1, Action<T1> action1,
+ ISourceBlock<T2> source2, Action<T2> action2,
+ ISourceBlock<T3> source3, Action<T3> action3,
+ DataflowBlockOptions dataflowBlockOptions)
+ {
+ Contract.Requires(source1 != null && action1 != null, "The first source and action should not be null.");
+ Contract.Requires(source2 != null && action2 != null, "The second source and action should not be null.");
+ Contract.Requires((source3 == null) == (action3 == null), "The third action should be null iff the third source is null.");
+ Contract.Requires(dataflowBlockOptions != null, "Options are required.");
+
+ bool hasThirdSource = source3 != null; // In the future, if we want higher arities on Choose, we can simply add more such checks on additional arguments
+
+ // Create object to act as both completion marker and sync obj for targets.
+ var boxedCompleted = new StrongBox<Task>();
+
+ // Set up teardown cancellation. We will request cancellation when a) the supplied options token
+ // has cancellation requested or b) when we actually complete somewhere in order to tear down
+ // the rest of our configured set up.
+ CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(dataflowBlockOptions.CancellationToken, CancellationToken.None);
+
+ // Set up the branches.
+ TaskScheduler scheduler = dataflowBlockOptions.TaskScheduler;
+ var branchTasks = new Task<int>[hasThirdSource ? 3 : 2];
+ branchTasks[0] = CreateChooseBranch(boxedCompleted, cts, scheduler, 0, source1, action1);
+ branchTasks[1] = CreateChooseBranch(boxedCompleted, cts, scheduler, 1, source2, action2);
+ if (hasThirdSource)
+ {
+ branchTasks[2] = CreateChooseBranch(boxedCompleted, cts, scheduler, 2, source3, action3);
+ }
+
+ // Asynchronously wait for all branches to complete, then complete
+ // a task to be returned to the caller.
+ var result = new TaskCompletionSource<int>();
+ Task.Factory.ContinueWhenAll(branchTasks, tasks =>
+ {
+ // Process the outcome of all branches. At most one will have completed
+ // successfully, returning its branch ID. Others may have faulted,
+ // in which case we need to propagate their exceptions, regardless
+ // of whether a branch completed successfully. Others may have been
+ // canceled (or run but found they were not needed), and those
+ // we just ignore.
+ List<Exception> exceptions = null;
+ int successfulBranchId = -1;
+ foreach (Task<int> task in tasks)
+ {
+ switch (task.Status)
+ {
+ case TaskStatus.Faulted:
+ Common.AddException(ref exceptions, task.Exception, unwrapInnerExceptions: true);
+ break;
+ case TaskStatus.RanToCompletion:
+ int resultBranchId = task.Result;
+ if (resultBranchId >= 0)
+ {
+ Debug.Assert(resultBranchId < tasks.Length, "Expected a valid branch ID");
+ Debug.Assert(successfulBranchId == -1, "There should be at most one successful branch.");
+ successfulBranchId = resultBranchId;
+ }
+ else Debug.Assert(resultBranchId == -1, "Expected -1 as a signal of a non-successful branch");
+ break;
+ }
+ }
+
+ // If we found any exceptions, fault the Choose task. Otherwise, if any branch completed
+ // successfully, store its result, or if cancellation was request
+ if (exceptions != null)
+ {
+ result.TrySetException(exceptions);
+ }
+ else if (successfulBranchId >= 0)
+ {
+ result.TrySetResult(successfulBranchId);
+ }
+ else
+ {
+ result.TrySetCanceled();
+ }
+
+ // By now we know that all of the tasks have completed, so there
+ // can't be any more use of the CancellationTokenSource.
+ cts.Dispose();
+ }, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default);
+ return result.Task;
+ }
+
+ /// <summary>Creates a target for a branch of a Choose.</summary>
+ /// <typeparam name="T">Specifies the type of data coming through this branch.</typeparam>
+ /// <param name="boxedCompleted">A strong box around the completed Task from any target. Also sync obj for access to the targets.</param>
+ /// <param name="cts">The CancellationTokenSource used to issue tear down / cancellation requests.</param>
+ /// <param name="scheduler">The TaskScheduler on which to scheduler work.</param>
+ /// <param name="branchId">The ID of this branch, used to complete the resultTask.</param>
+ /// <param name="source">The source with which this branch is associated.</param>
+ /// <param name="action">The action to run for a single element received from the source.</param>
+ /// <returns>A task representing the branch.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static Task<int> CreateChooseBranch<T>(
+ StrongBox<Task> boxedCompleted, CancellationTokenSource cts,
+ TaskScheduler scheduler,
+ int branchId, ISourceBlock<T> source, Action<T> action)
+ {
+ // If the cancellation token is already canceled, there is no need to create and link a target.
+ // Instead, directly return a canceled task.
+ if (cts.IsCancellationRequested)
+ return Common.CreateTaskFromCancellation<int>(cts.Token);
+
+ // Proceed with creating and linking a hidden target. Also get the source's completion task,
+ // as we need it to know when the source completes. Both of these operations
+ // could throw an exception if the block is faulty.
+ var target = new ChooseTarget<T>(boxedCompleted, cts.Token);
+ IDisposable unlink;
+ try
+ {
+ unlink = source.LinkTo(target, DataflowLinkOptions.UnlinkAfterOneAndPropagateCompletion);
+ }
+ catch (Exception exc)
+ {
+ cts.Cancel();
+ return Common.CreateTaskFromException<int>(exc);
+ }
+
+ // The continuation task below is implicitly capturing the right execution context,
+ // as CreateChooseBranch is called synchronously from Choose, so we
+ // don't need to additionally capture and marshal an ExecutionContext.
+
+ return target.Task.ContinueWith(completed =>
+ {
+ try
+ {
+ // If the target ran to completion, i.e. it got a message,
+ // cancel the other branch(es) and proceed with the user callback.
+ if (completed.Status == TaskStatus.RanToCompletion)
+ {
+ // Cancel the cts to trigger completion of the other branches.
+ cts.Cancel();
+
+ // Proceed with the user callback.
+ action(completed.Result);
+
+ // Return the ID of our branch to indicate.
+ return branchId;
+ }
+ return -1;
+ }
+ finally
+ {
+ // Unlink from the source. This could throw if the block is faulty,
+ // in which case our branch's task will fault. If this
+ // does throw, it'll end up propagating instead of the
+ // original action's exception if there was one.
+ unlink.Dispose();
+ }
+ }, CancellationToken.None, Common.GetContinuationOptions(), scheduler);
+ }
+
+ /// <summary>Provides a dataflow target used by Choose to receive data from a single source.</summary>
+ /// <typeparam name="T">Specifies the type of data offered to this target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ private sealed class ChooseTarget<T> : TaskCompletionSource<T>, ITargetBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>
+ /// Delegate used to invoke the action for a branch when that branch is activated
+ /// on the fast path.
+ /// </summary>
+ internal static readonly Func<object, int> s_processBranchFunction = state =>
+ {
+ Tuple<Action<T>, T, int> actionResultBranch = (Tuple<Action<T>, T, int>)state;
+ actionResultBranch.Item1(actionResultBranch.Item2);
+ return actionResultBranch.Item3;
+ };
+
+ /// <summary>
+ /// A wrapper for the task that represents the completed branch of this choice.
+ /// The wrapper is also the sync object used to protect all choice branch's access to shared state.
+ /// </summary>
+ private StrongBox<Task> _completed;
+
+ /// <summary>Initializes the target.</summary>
+ /// <param name="completed">The completed wrapper shared between all choice branches.</param>
+ /// <param name="cancellationToken">The cancellation token used to cancel this target.</param>
+ internal ChooseTarget(StrongBox<Task> completed, CancellationToken cancellationToken)
+ {
+ Contract.Requires(completed != null, "Requires a shared target to complete.");
+ _completed = completed;
+
+ // Handle async cancellation by canceling the target without storing it into _completed.
+ // _completed must only be set to a RanToCompletion task for a successful branch.
+ Common.WireCancellationToComplete(cancellationToken, base.Task,
+ state =>
+ {
+ var thisChooseTarget = (ChooseTarget<T>)state;
+ lock (thisChooseTarget._completed) thisChooseTarget.TrySetCanceled();
+ }, this);
+ }
+
+ /// <summary>Called when this choice branch is being offered a message.</summary>
+ public DataflowMessageStatus 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 (_completed)
+ {
+ // If we or another participating choice has already completed, we're done.
+ if (_completed.Value != null || base.Task.IsCompleted) return DataflowMessageStatus.DecliningPermanently;
+
+ // Consume the message from the source if necessary
+ if (consumeToAccept)
+ {
+ bool consumed;
+ messageValue = source.ConsumeMessage(messageHeader, this, out consumed);
+ if (!consumed) return DataflowMessageStatus.NotAvailable;
+ }
+
+ // Store the result and signal our success
+ TrySetResult(messageValue);
+ _completed.Value = Task;
+ return DataflowMessageStatus.Accepted;
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete()
+ {
+ lock (_completed) TrySetCanceled();
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void IDataflowBlock.Fault(Exception exception) { ((IDataflowBlock)this).Complete(); }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ return string.Format("{0} IsCompleted={1}",
+ Common.GetNameForDebugger(this), base.Task.IsCompleted);
+ }
+ }
+ /// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
+ object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
+ }
+ #endregion
+ #endregion
+
+ #region AsObservable
+ /// <summary>Creates a new <see cref="System.IObservable{TOutput}"/> abstraction over the <see cref="ISourceBlock{TOutput}"/>.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
+ /// <param name="source">The source to wrap.</param>
+ /// <returns>An IObservable{TOutput} that enables observers to be subscribed to the source.</returns>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
+ public static IObservable<TOutput> AsObservable<TOutput>(this ISourceBlock<TOutput> source)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+ return SourceObservable<TOutput>.From(source);
+ }
+
+ /// <summary>Cached options for non-greedy processing.</summary>
+ private static readonly ExecutionDataflowBlockOptions _nonGreedyExecutionOptions = new ExecutionDataflowBlockOptions { BoundedCapacity = 1 };
+
+ /// <summary>Provides an IObservable veneer over a source block.</summary>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(SourceObservable<>.DebugView))]
+ private sealed class SourceObservable<TOutput> : IObservable<TOutput>, IDebuggerDisplay
+ {
+ /// <summary>The table that maps source to cached observable.</summary>
+ /// <remarks>
+ /// ConditionalWeakTable doesn't do the initialization under a lock, just the publication.
+ /// This means that if there's a race to create two observables off the same source, we could end
+ /// up instantiating multiple SourceObservable instances, of which only one will be published.
+ /// Worst case, we end up with a few additional continuations off of the source's completion task.
+ /// </remarks>
+ private static readonly ConditionalWeakTable<ISourceBlock<TOutput>, SourceObservable<TOutput>> _table =
+ new ConditionalWeakTable<ISourceBlock<TOutput>, SourceObservable<TOutput>>();
+
+ /// <summary>Gets an observable to represent the source block.</summary>
+ /// <param name="source">The source.</param>
+ /// <returns>The observable.</returns>
+ internal static IObservable<TOutput> From(ISourceBlock<TOutput> source)
+ {
+ Contract.Requires(source != null, "Requires a source for which to retrieve the observable.");
+ return _table.GetValue(source, s => new SourceObservable<TOutput>(s));
+ }
+
+ /// <summary>Object used to synchronize all subscriptions, unsubscriptions, and propagations.</summary>
+ private readonly object _SubscriptionLock = new object();
+ /// <summary>The wrapped source.</summary>
+ private readonly ISourceBlock<TOutput> _source;
+ /// <summary>
+ /// The current target. We use the same target until the number of subscribers
+ /// drops to 0, at which point we substitute in a new target.
+ /// </summary>
+ private ObserversState _observersState;
+
+ /// <summary>Initializes the SourceObservable.</summary>
+ /// <param name="source">The source to wrap.</param>
+ internal SourceObservable(ISourceBlock<TOutput> source)
+ {
+ Contract.Requires(source != null, "The observable requires a source to wrap.");
+ _source = source;
+ _observersState = new ObserversState(this);
+ }
+
+ /// <summary>Gets any exceptions from the source block.</summary>
+ /// <returns>The aggregate exception of all errors, or null if everything completed successfully.</returns>
+ private AggregateException GetCompletionError()
+ {
+ Task sourceCompletionTask = Common.GetPotentiallyNotSupportedCompletionTask(_source);
+ return sourceCompletionTask != null && sourceCompletionTask.IsFaulted ?
+ sourceCompletionTask.Exception : null;
+ }
+
+ /// <summary>Subscribes the observer to the source.</summary>
+ /// <param name="observer">the observer to subscribe.</param>
+ /// <returns>An IDisposable that may be used to unsubscribe the source.</returns>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+ IDisposable IObservable<TOutput>.Subscribe(IObserver<TOutput> observer)
+ {
+ // Validate arguments
+ if (observer == null) throw new ArgumentNullException("observer");
+ Contract.EndContractBlock();
+ Common.ContractAssertMonitorStatus(_SubscriptionLock, held: false);
+
+ Task sourceCompletionTask = Common.GetPotentiallyNotSupportedCompletionTask(_source);
+
+ // Synchronize all observers for this source.
+ Exception error = null;
+ lock (_SubscriptionLock)
+ {
+ // Fast path for if everything is already done. We need to ensure that both
+ // the source is complete and that the target has finished propagating data to all observers.
+ // If there was an error, we grab it here and then we'll complete the observer
+ // outside of the lock.
+ if (sourceCompletionTask != null && sourceCompletionTask.IsCompleted &&
+ _observersState.Target.Completion.IsCompleted)
+ {
+ error = GetCompletionError();
+ }
+ // Otherwise, we need to subscribe this observer.
+ else
+ {
+ // Hook up the observer. If this is the first observer, link the source to the target.
+ _observersState.Observers = _observersState.Observers.Add(observer);
+ if (_observersState.Observers.Count == 1)
+ {
+ Debug.Assert(_observersState.Unlinker == null, "The source should not be linked to the target.");
+ _observersState.Unlinker = _source.LinkTo(_observersState.Target);
+ if (_observersState.Unlinker == null)
+ {
+ _observersState.Observers = ImmutableList<IObserver<TOutput>>.Empty;
+ return null;
+ }
+ }
+
+ // Return a disposable that will unlink this observer, and if it's the last
+ // observer for the source, shut off the pipe to observers.
+ return Disposables.Create((s, o) => s.Unsubscribe(o), this, observer);
+ }
+ }
+
+ // Complete the observer.
+ if (error != null) observer.OnError(error);
+ else observer.OnCompleted();
+ return Disposables.Nop;
+ }
+
+ /// <summary>Unsubscribes the observer.</summary>
+ /// <param name="observer">The observer being unsubscribed.</param>
+ private void Unsubscribe(IObserver<TOutput> observer)
+ {
+ Contract.Requires(observer != null, "Expected an observer.");
+ Common.ContractAssertMonitorStatus(_SubscriptionLock, held: false);
+
+ lock (_SubscriptionLock)
+ {
+ ObserversState currentState = _observersState;
+ Debug.Assert(currentState != null, "Observer state should never be null.");
+
+ // If the observer was already unsubscribed (or is otherwise no longer present in our list), bail.
+ if (!currentState.Observers.Contains(observer)) return;
+
+ // If this is the last observer being removed, reset to be ready for future subscribers.
+ if (currentState.Observers.Count == 1)
+ {
+ ResetObserverState();
+ }
+ // Otherwise, just remove the observer. Note that we don't remove the observer
+ // from the current target if this is the last observer. This is done in case the target
+ // has already taken data from the source: we want that data to end up somewhere,
+ // and we can't put it back in the source, so we ensure we send it along to the observer.
+ else
+ {
+ currentState.Observers = currentState.Observers.Remove(observer);
+ }
+ }
+ }
+
+ /// <summary>Resets the observer state to the original, inactive state.</summary>
+ /// <returns>The list of active observers prior to the reset.</returns>
+ private ImmutableList<IObserver<TOutput>> ResetObserverState()
+ {
+ Common.ContractAssertMonitorStatus(_SubscriptionLock, held: true);
+
+ ObserversState currentState = _observersState;
+ Debug.Assert(currentState != null, "Observer state should never be null.");
+ Debug.Assert(currentState.Unlinker != null, "The target should be linked.");
+ Debug.Assert(currentState.Canceler != null, "The target should have set up continuations.");
+
+ // Replace the target with a clean one, unlink and cancel, and return the previous set of observers
+ ImmutableList<IObserver<TOutput>> currentObservers = currentState.Observers;
+ _observersState = new ObserversState(this);
+ currentState.Unlinker.Dispose();
+ currentState.Canceler.Cancel();
+ return currentObservers;
+ }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displaySource = _source as IDebuggerDisplay;
+ return string.Format("Observers={0}, Block=\"{1}\"",
+ _observersState.Observers.Count,
+ displaySource != null ? displaySource.Content : _source);
+ }
+ }
+ /// <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 observable.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The observable being debugged.</summary>
+ private readonly SourceObservable<TOutput> _observable;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="observable">The target being debugged.</param>
+ public DebugView(SourceObservable<TOutput> observable)
+ {
+ Contract.Requires(observable != null, "Need a block with which to construct the debug view.");
+ _observable = observable;
+ }
+
+ /// <summary>Gets an enumerable of the observers.</summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public IObserver<TOutput>[] Observers { get { return _observable._observersState.Observers.ToArray(); } }
+ }
+
+ /// <summary>State associated with the current target for propagating data to observers.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ private sealed class ObserversState
+ {
+ /// <summary>The owning SourceObservable.</summary>
+ internal readonly SourceObservable<TOutput> Observable;
+ /// <summary>The ActionBlock that consumes data from a source and offers it to targets.</summary>
+ internal readonly ActionBlock<TOutput> Target;
+ /// <summary>Used to cancel continuations when they're no longer necessary.</summary>
+ internal readonly CancellationTokenSource Canceler = new CancellationTokenSource();
+ /// <summary>
+ /// A list of the observers currently registered with this target. The list is immutable
+ /// to enable iteration through the list while the set of observers may be changing.
+ /// </summary>
+ internal ImmutableList<IObserver<TOutput>> Observers = ImmutableList<IObserver<TOutput>>.Empty;
+ /// <summary>Used to unlink the source from this target when the last observer is unsubscribed.</summary>
+ internal IDisposable Unlinker;
+ /// <summary>
+ /// Temporary list to keep track of SendAsync tasks to TargetObservers with back pressure.
+ /// This field gets instantiated on demand. It gets populated and cleared within an offering cycle.
+ /// </summary>
+ private List<Task<bool>> _tempSendAsyncTaskList;
+
+ /// <summary>Initializes the target instance.</summary>
+ /// <param name="observable">The owning observable.</param>
+ internal ObserversState(SourceObservable<TOutput> observable)
+ {
+ Contract.Requires(observable != null, "Observe state must be mapped to a source observable.");
+
+ // Set up the target block
+ Observable = observable;
+ Target = new ActionBlock<TOutput>((Func<TOutput, Task>)ProcessItemAsync, DataflowBlock._nonGreedyExecutionOptions);
+
+ // If the target block fails due to an unexpected exception (e.g. it calls back to the source and the source throws an error),
+ // we fault currently registered observers and reset the observable.
+ Target.Completion.ContinueWith(
+ (t, state) => ((ObserversState)state).NotifyObserversOfCompletion(t.Exception), this,
+ CancellationToken.None,
+ Common.GetContinuationOptions(TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously),
+ TaskScheduler.Default);
+
+ // When the source completes, complete the target. Then when the target completes,
+ // send completion messages to any observers still registered.
+ Task sourceCompletionTask = Common.GetPotentiallyNotSupportedCompletionTask(Observable._source);
+ if (sourceCompletionTask != null)
+ {
+ sourceCompletionTask.ContinueWith((_1, state1) =>
+ {
+ var ti = (ObserversState)state1;
+ ti.Target.Complete();
+ ti.Target.Completion.ContinueWith(
+ (_2, state2) => ((ObserversState)state2).NotifyObserversOfCompletion(), state1,
+ CancellationToken.None,
+ Common.GetContinuationOptions(TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.ExecuteSynchronously),
+ TaskScheduler.Default);
+ }, this, Canceler.Token, Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>Forwards an item to all currently subscribed observers.</summary>
+ /// <param name="item">The item to forward.</param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private Task ProcessItemAsync(TOutput item)
+ {
+ Common.ContractAssertMonitorStatus(Observable._SubscriptionLock, held: false);
+
+ ImmutableList<IObserver<TOutput>> currentObservers;
+ lock (Observable._SubscriptionLock) currentObservers = Observers;
+ try
+ {
+ foreach (IObserver<TOutput> observer in currentObservers)
+ {
+ // If the observer is our own TargetObserver, we SendAsync() to it
+ // rather than going through IObserver.OnNext() which allows us to
+ // continue offering to the remaining observers without blocking.
+ var targetObserver = observer as TargetObserver<TOutput>;
+ if (targetObserver != null)
+ {
+ Task<bool> sendAsyncTask = targetObserver.SendAsyncToTarget(item);
+ if (sendAsyncTask.Status != TaskStatus.RanToCompletion)
+ {
+ // Ensure the SendAsyncTaskList is instantiated
+ if (_tempSendAsyncTaskList == null) _tempSendAsyncTaskList = new List<Task<bool>>();
+
+ // Add the task to the list
+ _tempSendAsyncTaskList.Add(sendAsyncTask);
+ }
+ }
+ else
+ {
+ observer.OnNext(item);
+ }
+ }
+
+ // If there are SendAsync tasks to wait on...
+ if (_tempSendAsyncTaskList != null && _tempSendAsyncTaskList.Count > 0)
+ {
+ // Consolidate all SendAsync tasks into one
+ Task<bool[]> allSendAsyncTasksConsolidated = Task.WhenAll(_tempSendAsyncTaskList);
+
+ // Clear the temp SendAsync task list
+ _tempSendAsyncTaskList.Clear();
+
+ // Return the consolidated task
+ return allSendAsyncTasksConsolidated;
+ }
+ }
+ catch (Exception exc)
+ {
+ // Return a faulted task
+ return Common.CreateTaskFromException<VoidResult>(exc);
+ }
+
+ // All observers accepted normally.
+ // Return a completed task.
+ return Common.CompletedTaskWithTrueResult;
+ }
+
+ /// <summary>Notifies all currently registered observers that they should complete.</summary>
+ /// <param name="targetException">
+ /// Non-null when an unexpected exception occurs during processing. Faults
+ /// all subscribed observers and resets the observable back to its original condition.
+ /// </param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void NotifyObserversOfCompletion(Exception targetException = null)
+ {
+ Contract.Requires(Target.Completion.IsCompleted, "The target must have already completed in order to notify of completion.");
+ Common.ContractAssertMonitorStatus(Observable._SubscriptionLock, held: false);
+
+ // Send completion notification to all observers.
+ ImmutableList<IObserver<TOutput>> currentObservers;
+ lock (Observable._SubscriptionLock)
+ {
+ // Get the currently registered set of observers. Then, if we're being called due to the target
+ // block failing from an unexpected exception, reset the observer state so that subsequent
+ // subscribed observers will get a new target block. Finally clear out our observer list.
+ currentObservers = Observers;
+ if (targetException != null) Observable.ResetObserverState();
+ Observers = ImmutableList<IObserver<TOutput>>.Empty;
+ }
+
+ // If there are any observers to complete...
+ if (currentObservers.Count > 0)
+ {
+ // Determine if we should fault or complete the observers
+ Exception error = targetException ?? Observable.GetCompletionError();
+ try
+ {
+ // Do it.
+ if (error != null)
+ {
+ foreach (IObserver<TOutput> observer in currentObservers) observer.OnError(error);
+ }
+ else
+ {
+ foreach (IObserver<TOutput> observer in currentObservers) observer.OnCompleted();
+ }
+ }
+ catch (Exception exc)
+ {
+ // If an observer throws an exception at this point (which it shouldn't do),
+ // we have little recourse but to let that exception propagate. Since allowing it to
+ // propagate here would just result in it getting eaten by the owning task,
+ // we instead have it propagate on the thread pool.
+ Common.ThrowAsync(exc);
+ }
+ }
+ }
+ }
+ }
+ #endregion
+
+ #region AsObserver
+ /// <summary>Creates a new <see cref="System.IObserver{TInput}"/> abstraction over the <see cref="ITargetBlock{TInput}"/>.</summary>
+ /// <typeparam name="TInput">Specifies the type of input accepted by the target block.</typeparam>
+ /// <param name="target">The target to wrap.</param>
+ /// <returns>An observer that wraps the target block.</returns>
+ public static IObserver<TInput> AsObserver<TInput>(this ITargetBlock<TInput> target)
+ {
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+ return new TargetObserver<TInput>(target);
+ }
+
+ /// <summary>Provides an observer wrapper for a target block.</summary>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ private sealed class TargetObserver<TInput> : IObserver<TInput>, IDebuggerDisplay
+ {
+ /// <summary>The wrapped target.</summary>
+ private readonly ITargetBlock<TInput> _target;
+
+ /// <summary>Initializes the observer.</summary>
+ /// <param name="target">The target to wrap.</param>
+ internal TargetObserver(ITargetBlock<TInput> target)
+ {
+ Contract.Requires(target != null, "A target to observe is required.");
+ _target = target;
+ }
+
+ /// <summary>Sends the value to the observer.</summary>
+ /// <param name="value">The value to send.</param>
+ void IObserver<TInput>.OnNext(TInput value)
+ {
+ // Send the value asynchronously...
+ Task<bool> task = SendAsyncToTarget(value);
+
+ // And block until it's received.
+ task.GetAwaiter().GetResult(); // propagate original (non-aggregated) exception
+ }
+
+ /// <summary>Completes the target.</summary>
+ void IObserver<TInput>.OnCompleted()
+ {
+ _target.Complete();
+ }
+
+ /// <summary>Forwards the error to the target.</summary>
+ /// <param name="error">The exception to forward.</param>
+ void IObserver<TInput>.OnError(Exception error)
+ {
+ _target.Fault(error);
+ }
+
+ /// <summary>Sends a value to the underlying target asynchronously.</summary>
+ /// <param name="value">The value to send.</param>
+ /// <returns>A Task{bool} to wait on.</returns>
+ internal Task<bool> SendAsyncToTarget(TInput value)
+ {
+ return _target.SendAsync(value);
+ }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayTarget = _target as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displayTarget != null ? displayTarget.Content : _target);
+ }
+ }
+ /// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
+ object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
+ }
+ #endregion
+
+ #region NullTarget
+ /// <summary>
+ /// Gets a target block that synchronously accepts all messages offered to it and drops them.
+ /// </summary>
+ /// <typeparam name="TInput">The type of the messages this block can accept.</typeparam>
+ /// <returns>A <see cref="T:System.Threading.Tasks.Dataflow.ITargetBlock`1"/> that accepts and subsequently drops all offered messages.</returns>
+ public static ITargetBlock<TInput> NullTarget<TInput>()
+ {
+ return new NullTargetBlock<TInput>();
+ }
+
+ /// <summary>
+ /// Target block that synchronously accepts all messages offered to it and drops them.
+ /// </summary>
+ /// <typeparam name="TInput">The type of the messages this block can accept.</typeparam>
+ private class NullTargetBlock<TInput> : ITargetBlock<TInput>
+ {
+ private Task _completion;
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ DataflowMessageStatus ITargetBlock<TInput>.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, Boolean consumeToAccept)
+ {
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ Contract.EndContractBlock();
+
+ // If the source requires an explicit synchronous consumption, do it
+ if (consumeToAccept)
+ {
+ if (source == null) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept");
+ bool messageConsumed;
+
+ // If the source throws during this call, let the exception propagate back to the source
+ source.ConsumeMessage(messageHeader, this, out messageConsumed);
+ if (!messageConsumed) return DataflowMessageStatus.NotAvailable;
+ }
+
+ // Always tell the source the message has been accepted
+ return DataflowMessageStatus.Accepted;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete() { } // No-op
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void IDataflowBlock.Fault(Exception exception) { } // No-op
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion
+ {
+ get { return LazyInitializer.EnsureInitialized(ref _completion, () => new TaskCompletionSource<VoidResult>().Task); }
+ }
+ }
+ #endregion
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// DataflowBlockOptions.cs
+//
+//
+// DataflowBlockOptions types for configuring dataflow blocks
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>
+ /// Provides options used to configure the processing performed by dataflow blocks.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="DataflowBlockOptions"/> is mutable and can be configured through its properties.
+ /// When specific configuration options are not set, the following defaults are used:
+ /// <list type="table">
+ /// <listheader>
+ /// <term>Options</term>
+ /// <description>Default</description>
+ /// </listheader>
+ /// <item>
+ /// <term>TaskScheduler</term>
+ /// <description><see cref="System.Threading.Tasks.TaskScheduler.Default"/></description>
+ /// </item>
+ /// <item>
+ /// <term>MaxMessagesPerTask</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>CancellationToken</term>
+ /// <description><see cref="System.Threading.CancellationToken.None"/></description>
+ /// </item>
+ /// <item>
+ /// <term>BoundedCapacity</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>NameFormat</term>
+ /// <description>"{0} Id={1}"</description>
+ /// </item>
+ /// </list>
+ /// Dataflow blocks capture the state of the options at their construction. Subsequent changes
+ /// to the provided <see cref="DataflowBlockOptions"/> instance should not affect the behavior
+ /// of a dataflow block.
+ /// </remarks>
+ [DebuggerDisplay("TaskScheduler = {TaskScheduler}, MaxMessagesPerTask = {MaxMessagesPerTask}, BoundedCapacity = {BoundedCapacity}")]
+ public class DataflowBlockOptions
+ {
+ /// <summary>
+ /// A constant used to specify an unlimited quantity for <see cref="DataflowBlockOptions"/> members
+ /// that provide an upper bound. This field is constant.
+ /// </summary>
+ public const Int32 Unbounded = -1;
+
+ /// <summary>The scheduler to use for scheduling tasks to process messages.</summary>
+ private TaskScheduler _taskScheduler = TaskScheduler.Default;
+ /// <summary>The cancellation token to monitor for cancellation requests.</summary>
+ private CancellationToken _cancellationToken = CancellationToken.None;
+ /// <summary>The maximum number of messages that may be processed per task.</summary>
+ private Int32 _maxMessagesPerTask = Unbounded;
+ /// <summary>The maximum number of messages that may be buffered by the block.</summary>
+ private Int32 _boundedCapacity = Unbounded;
+ /// <summary>The name format to use for creating a name for a block.</summary>
+ private string _nameFormat = "{0} Id={1}"; // see NameFormat property for a description of format items
+
+ /// <summary>A default instance of <see cref="DataflowBlockOptions"/>.</summary>
+ /// <remarks>
+ /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user.
+ /// </remarks>
+ internal static readonly DataflowBlockOptions Default = new DataflowBlockOptions();
+
+ /// <summary>Returns this <see cref="DataflowBlockOptions"/> instance if it's the default instance or else a cloned instance.</summary>
+ /// <returns>An instance of the options that may be cached by the block.</returns>
+ internal DataflowBlockOptions DefaultOrClone()
+ {
+ return (this == Default) ?
+ this :
+ new DataflowBlockOptions
+ {
+ TaskScheduler = this.TaskScheduler,
+ CancellationToken = this.CancellationToken,
+ MaxMessagesPerTask = this.MaxMessagesPerTask,
+ BoundedCapacity = this.BoundedCapacity,
+ NameFormat = this.NameFormat
+ };
+ }
+
+ /// <summary>Initializes the <see cref="DataflowBlockOptions"/>.</summary>
+ public DataflowBlockOptions() { }
+
+ /// <summary>Gets or sets the <see cref="System.Threading.Tasks.TaskScheduler"/> to use for scheduling tasks.</summary>
+ public TaskScheduler TaskScheduler
+ {
+ get { return _taskScheduler; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ if (value == null) throw new ArgumentNullException("value");
+ _taskScheduler = value;
+ }
+ }
+
+ /// <summary>Gets or sets the <see cref="System.Threading.CancellationToken"/> to monitor for cancellation requests.</summary>
+ public CancellationToken CancellationToken
+ {
+ get { return _cancellationToken; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ _cancellationToken = value;
+ }
+ }
+
+ /// <summary>Gets or sets the maximum number of messages that may be processed per task.</summary>
+ public Int32 MaxMessagesPerTask
+ {
+ get { return _maxMessagesPerTask; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value");
+ _maxMessagesPerTask = value;
+ }
+ }
+
+ /// <summary>Gets a MaxMessagesPerTask value that may be used for comparison purposes.</summary>
+ /// <returns>The maximum value, usable for comparison purposes.</returns>
+ /// <remarks>Unlike MaxMessagesPerTask, this property will always return a positive value.</remarks>
+ internal Int32 ActualMaxMessagesPerTask
+ {
+ get { return (_maxMessagesPerTask == Unbounded) ? Int32.MaxValue : _maxMessagesPerTask; }
+ }
+
+ /// <summary>Gets or sets the maximum number of messages that may be buffered by the block.</summary>
+ public Int32 BoundedCapacity
+ {
+ get { return _boundedCapacity; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value");
+ _boundedCapacity = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the format string to use when a block is queried for its name.
+ /// </summary>
+ /// <remarks>
+ /// The name format may contain up to two format items. {0} will be substituted
+ /// with the block's name. {1} will be substituted with the block's Id, as is
+ /// returned from the block's Completion.Id property.
+ /// </remarks>
+ public string NameFormat
+ {
+ get { return _nameFormat; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ if (value == null) throw new ArgumentNullException("value");
+ _nameFormat = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Provides options used to configure the processing performed by dataflow blocks that
+ /// process each message through the invocation of a user-provided delegate, blocks such
+ /// as <see cref="ActionBlock{T}"/> and <see cref="TransformBlock{TInput,TOutput}"/>.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="ExecutionDataflowBlockOptions"/> is mutable and can be configured through its properties.
+ /// When specific configuration options are not set, the following defaults are used:
+ /// <list type="table">
+ /// <listheader>
+ /// <term>Options</term>
+ /// <description>Default</description>
+ /// </listheader>
+ /// <item>
+ /// <term>TaskScheduler</term>
+ /// <description><see cref="System.Threading.Tasks.TaskScheduler.Default"/></description>
+ /// </item>
+ /// <item>
+ /// <term>CancellationToken</term>
+ /// <description><see cref="System.Threading.CancellationToken.None"/></description>
+ /// </item>
+ /// <item>
+ /// <term>MaxMessagesPerTask</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>BoundedCapacity</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>NameFormat</term>
+ /// <description>"{0} Id={1}"</description>
+ /// </item>
+ /// <item>
+ /// <term>MaxDegreeOfParallelism</term>
+ /// <description>1</description>
+ /// </item>
+ /// <item>
+ /// <term>SingleProducerConstrained</term>
+ /// <description>false</description>
+ /// </item>
+ /// </list>
+ /// Dataflow block captures the state of the options at their construction. Subsequent changes
+ /// to the provided <see cref="ExecutionDataflowBlockOptions"/> instance should not affect the behavior
+ /// of a dataflow block.
+ /// </remarks>
+ [DebuggerDisplay("TaskScheduler = {TaskScheduler}, MaxMessagesPerTask = {MaxMessagesPerTask}, BoundedCapacity = {BoundedCapacity}, MaxDegreeOfParallelism = {MaxDegreeOfParallelism}")]
+ public class ExecutionDataflowBlockOptions : DataflowBlockOptions
+ {
+ /// <summary>A default instance of <see cref="DataflowBlockOptions"/>.</summary>
+ /// <remarks>
+ /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user.
+ /// </remarks>
+ internal new static readonly ExecutionDataflowBlockOptions Default = new ExecutionDataflowBlockOptions();
+
+ /// <summary>Returns this <see cref="ExecutionDataflowBlockOptions"/> instance if it's the default instance or else a cloned instance.</summary>
+ /// <returns>An instance of the options that may be cached by the block.</returns>
+ internal new ExecutionDataflowBlockOptions DefaultOrClone()
+ {
+ return (this == Default) ?
+ this :
+ new ExecutionDataflowBlockOptions
+ {
+ TaskScheduler = this.TaskScheduler,
+ CancellationToken = this.CancellationToken,
+ MaxMessagesPerTask = this.MaxMessagesPerTask,
+ BoundedCapacity = this.BoundedCapacity,
+ NameFormat = this.NameFormat,
+ MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
+ SingleProducerConstrained = this.SingleProducerConstrained
+ };
+ }
+
+ /// <summary>The maximum number of tasks that may be used concurrently to process messages.</summary>
+ private Int32 _maxDegreeOfParallelism = 1;
+ /// <summary>Whether the code using this block will only ever have a single producer accessing the block at any given time.</summary>
+ private Boolean _singleProducerConstrained = false;
+
+ /// <summary>Initializes the <see cref="ExecutionDataflowBlockOptions"/>.</summary>
+ public ExecutionDataflowBlockOptions() { }
+
+ /// <summary>Gets the maximum number of messages that may be processed by the block concurrently.</summary>
+ public Int32 MaxDegreeOfParallelism
+ {
+ get { return _maxDegreeOfParallelism; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value");
+ _maxDegreeOfParallelism = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether code using the dataflow block is constrained to one producer at a time.
+ /// </summary>
+ /// <remarks>
+ /// This property defaults to false, such that the block may be used by multiple
+ /// producers concurrently. This property should only be set to true if the code
+ /// using the block can guarantee that it will only ever be used by one producer
+ /// (e.g. a source linked to the block) at a time, meaning that methods like Post,
+ /// Complete, Fault, and OfferMessage will never be called concurrently. Some blocks
+ /// may choose to capitalize on the knowledge that there will only be one producer at a time
+ /// in order to provide better performance.
+ /// </remarks>
+ public Boolean SingleProducerConstrained
+ {
+ get { return _singleProducerConstrained; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ _singleProducerConstrained = value;
+ }
+ }
+
+ /// <summary>Gets a MaxDegreeOfParallelism value that may be used for comparison purposes.</summary>
+ /// <returns>The maximum value, usable for comparison purposes.</returns>
+ /// <remarks>Unlike MaxDegreeOfParallelism, this property will always return a positive value.</remarks>
+ internal Int32 ActualMaxDegreeOfParallelism
+ {
+ get { return (_maxDegreeOfParallelism == Unbounded) ? Int32.MaxValue : _maxDegreeOfParallelism; }
+ }
+
+ /// <summary>Gets whether these dataflow block options allow for parallel execution.</summary>
+ internal Boolean SupportsParallelExecution { get { return _maxDegreeOfParallelism == Unbounded || _maxDegreeOfParallelism > 1; } }
+ }
+
+ /// <summary>
+ /// Provides options used to configure the processing performed by dataflow blocks that
+ /// group together multiple messages, blocks such as <see cref="JoinBlock{T1,T2}"/> and
+ /// <see cref="BatchBlock{T}"/>.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="GroupingDataflowBlockOptions"/> is mutable and can be configured through its properties.
+ /// When specific configuration options are not set, the following defaults are used:
+ /// <list type="table">
+ /// <listheader>
+ /// <term>Options</term>
+ /// <description>Default</description>
+ /// </listheader>
+ /// <item>
+ /// <term>TaskScheduler</term>
+ /// <description><see cref="System.Threading.Tasks.TaskScheduler.Default"/></description>
+ /// </item>
+ /// <item>
+ /// <term>CancellationToken</term>
+ /// <description><see cref="System.Threading.CancellationToken.None"/></description>
+ /// </item>
+ /// <item>
+ /// <term>MaxMessagesPerTask</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>BoundedCapacity</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>NameFormat</term>
+ /// <description>"{0} Id={1}"</description>
+ /// </item>
+ /// <item>
+ /// <term>MaxNumberOfGroups</term>
+ /// <description>GroupingDataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>Greedy</term>
+ /// <description>true</description>
+ /// </item>
+ /// </list>
+ /// Dataflow block capture the state of the options at their construction. Subsequent changes
+ /// to the provided <see cref="GroupingDataflowBlockOptions"/> instance should not affect the behavior
+ /// of a dataflow block.
+ /// </remarks>
+ [DebuggerDisplay("TaskScheduler = {TaskScheduler}, MaxMessagesPerTask = {MaxMessagesPerTask}, BoundedCapacity = {BoundedCapacity}, Greedy = {Greedy}, MaxNumberOfGroups = {MaxNumberOfGroups}")]
+ public class GroupingDataflowBlockOptions : DataflowBlockOptions
+ {
+ /// <summary>A default instance of <see cref="DataflowBlockOptions"/>.</summary>
+ /// <remarks>
+ /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user.
+ /// </remarks>
+ internal new static readonly GroupingDataflowBlockOptions Default = new GroupingDataflowBlockOptions();
+
+ /// <summary>Returns this <see cref="GroupingDataflowBlockOptions"/> instance if it's the default instance or else a cloned instance.</summary>
+ /// <returns>An instance of the options that may be cached by the block.</returns>
+ internal new GroupingDataflowBlockOptions DefaultOrClone()
+ {
+ return (this == Default) ?
+ this :
+ new GroupingDataflowBlockOptions
+ {
+ TaskScheduler = this.TaskScheduler,
+ CancellationToken = this.CancellationToken,
+ MaxMessagesPerTask = this.MaxMessagesPerTask,
+ BoundedCapacity = this.BoundedCapacity,
+ NameFormat = this.NameFormat,
+ Greedy = this.Greedy,
+ MaxNumberOfGroups = this.MaxNumberOfGroups
+ };
+ }
+
+ /// <summary>Whether the block should greedily consume offered messages.</summary>
+ private Boolean _greedy = true;
+ /// <summary>The maximum number of groups that should be generated by the block.</summary>
+ private Int64 _maxNumberOfGroups = Unbounded;
+
+ /// <summary>Initializes the <see cref="GroupingDataflowBlockOptions"/>.</summary>
+ public GroupingDataflowBlockOptions() { }
+
+ /// <summary>Gets or sets the Boolean value to use to determine whether to greedily consume offered messages.</summary>
+ public Boolean Greedy
+ {
+ get { return _greedy; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ _greedy = value;
+ }
+ }
+
+ /// <summary>Gets or sets the maximum number of groups that should be generated by the block.</summary>
+ public Int64 MaxNumberOfGroups
+ {
+ get { return _maxNumberOfGroups; }
+ set
+ {
+ Debug.Assert(this != Default, "Default instance is supposed to be immutable.");
+ if (value <= 0 && value != Unbounded) throw new ArgumentOutOfRangeException("value");
+ _maxNumberOfGroups = value;
+ }
+ }
+
+ /// <summary>Gets a MaxNumberOfGroups value that may be used for comparison purposes.</summary>
+ /// <returns>The maximum value, usable for comparison purposes.</returns>
+ /// <remarks>Unlike MaxNumberOfGroups, this property will always return a positive value.</remarks>
+ internal Int64 ActualMaxNumberOfGroups
+ {
+ get { return (_maxNumberOfGroups == Unbounded) ? Int64.MaxValue : _maxNumberOfGroups; }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// DataflowLinkOptions.cs
+//
+//
+// DataflowLinkOptions type for configuring links between dataflow blocks
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>
+ /// Provides options used to configure a link between dataflow blocks.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="DataflowLinkOptions"/> is mutable and can be configured through its properties.
+ /// When specific configuration options are not set, the following defaults are used:
+ /// <list type="table">
+ /// <listheader>
+ /// <term>Options</term>
+ /// <description>Default</description>
+ /// </listheader>
+ /// <item>
+ /// <term>PropagateCompletion</term>
+ /// <description>False</description>
+ /// </item>
+ /// <item>
+ /// <term>MaxMessages</term>
+ /// <description>DataflowBlockOptions.Unbounded (-1)</description>
+ /// </item>
+ /// <item>
+ /// <term>Append</term>
+ /// <description>True</description>
+ /// </item>
+ /// </list>
+ /// Dataflow blocks capture the state of the options at linking. Subsequent changes to the provided
+ /// <see cref="DataflowLinkOptions"/> instance should not affect the behavior of a link.
+ /// </remarks>
+ [DebuggerDisplay("PropagateCompletion = {PropagateCompletion}, MaxMessages = {MaxMessages}, Append = {Append}")]
+ public class DataflowLinkOptions
+ {
+ /// <summary>
+ /// A constant used to specify an unlimited quantity for <see cref="DataflowLinkOptions"/> members
+ /// that provide an upper bound. This field is a constant tied to <see cref="DataflowLinkOptions.Unbounded"/>.
+ /// </summary>
+ internal const Int32 Unbounded = DataflowBlockOptions.Unbounded;
+
+ /// <summary>Whether the linked target will have completion and faulting notification propagated to it automatically.</summary>
+ private Boolean _propagateCompletion = false;
+ /// <summary>The maximum number of messages that may be consumed across the link.</summary>
+ private Int32 _maxNumberOfMessages = Unbounded;
+ /// <summary>Whether the link should be appended to the source’s list of links, or whether it should be prepended.</summary>
+ private Boolean _append = true;
+
+ /// <summary>A default instance of <see cref="DataflowLinkOptions"/>.</summary>
+ /// <remarks>
+ /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user.
+ /// </remarks>
+ internal static readonly DataflowLinkOptions Default = new DataflowLinkOptions();
+
+ /// <summary>A cached instance of <see cref="DataflowLinkOptions"/>.</summary>
+ /// <remarks>
+ /// Do not change the values of this instance. It is shared by all of our blocks that need to unlink after one message has been consumed.
+ /// </remarks>
+ internal static readonly DataflowLinkOptions UnlinkAfterOneAndPropagateCompletion = new DataflowLinkOptions() { MaxMessages = 1, PropagateCompletion = true };
+
+ /// <summary>Initializes the <see cref="DataflowLinkOptions"/>.</summary>
+ public DataflowLinkOptions()
+ {
+ }
+
+ /// <summary>Gets or sets whether the linked target will have completion and faulting notification propagated to it automatically.</summary>
+ public Boolean PropagateCompletion
+ {
+ get { return _propagateCompletion; }
+ set
+ {
+ Debug.Assert(this != Default && this != UnlinkAfterOneAndPropagateCompletion, "Default and UnlinkAfterOneAndPropagateCompletion instances are supposed to be immutable.");
+ _propagateCompletion = value;
+ }
+ }
+
+ /// <summary>Gets or sets the maximum number of messages that may be consumed across the link.</summary>
+ public Int32 MaxMessages
+ {
+ get { return _maxNumberOfMessages; }
+ set
+ {
+ Debug.Assert(this != Default && this != UnlinkAfterOneAndPropagateCompletion, "Default and UnlinkAfterOneAndPropagateCompletion instances are supposed to be immutable.");
+ if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value");
+ _maxNumberOfMessages = value;
+ }
+ }
+
+ /// <summary>Gets or sets whether the link should be appended to the source’s list of links, or whether it should be prepended.</summary>
+ public Boolean Append
+ {
+ get { return _append; }
+ set
+ {
+ Debug.Assert(this != Default && this != UnlinkAfterOneAndPropagateCompletion, "Default and UnlinkAfterOneAndPropagateCompletion instances are supposed to be immutable.");
+ _append = value;
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// DataflowMessageHeader.cs
+//
+//
+// A container of data attributes passed between dataflow blocks.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Threading.Tasks.Dataflow.Internal;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Provides a container of data attributes for passing between dataflow blocks.</summary>
+ [DebuggerDisplay("Id = {Id}")]
+ public struct DataflowMessageHeader : IEquatable<DataflowMessageHeader>
+ {
+ /// <summary>The message ID. Needs to be unique within the source.</summary>
+ private readonly long _id;
+
+ /// <summary>Initializes the <see cref="DataflowMessageHeader"/> with the specified attributes.</summary>
+ /// <param name="id">The ID of the message. Must be unique within the originating source block. Need not be globally unique.</param>
+ public DataflowMessageHeader(Int64 id)
+ {
+ if (id == default(long)) throw new ArgumentException(SR.Argument_InvalidMessageId, "id");
+ Contract.EndContractBlock();
+
+ _id = id;
+ }
+
+ /// <summary>Gets the validity of the message.</summary>
+ /// <returns>True if the ID of the message is different from 0. False if the ID of the message is 0</returns>
+ public Boolean IsValid { get { return _id != default(long); } }
+
+ /// <summary>Gets the ID of the message within the source.</summary>
+ /// <returns>The ID contained in the <see cref="DataflowMessageHeader"/> instance.</returns>
+ public Int64 Id { get { return _id; } }
+
+ // These overrides are required by the FX API Guidelines.
+ // NOTE: When these overrides are present, the compiler doesn't complain about statements
+ // like 'if (struct == null) ...' which will result in incorrect behavior at runtime.
+ // The product code should not use them. Instead, it should compare the Id properties.
+ // To verify that, every once in a while, comment out this region and build the product.
+ #region Comparison Operators
+ /// <summary>Checks two <see cref="DataflowMessageHeader"/> instances for equality by ID without boxing.</summary>
+ /// <param name="other">Another <see cref="DataflowMessageHeader"/> instance.</param>
+ /// <returns>True if the instances are equal. False otherwise.</returns>
+ public bool Equals(DataflowMessageHeader other)
+ {
+ return this == other;
+ }
+
+ /// <summary>Checks boxed <see cref="DataflowMessageHeader"/> instances for equality by ID.</summary>
+ /// <param name="obj">A boxed <see cref="DataflowMessageHeader"/> instance.</param>
+ /// <returns>True if the instances are equal. False otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ return obj is DataflowMessageHeader && this == (DataflowMessageHeader)obj;
+ }
+
+ /// <summary>Generates a hash code for the <see cref="DataflowMessageHeader"/> instance.</summary>
+ /// <returns>Hash code.</returns>
+ public override int GetHashCode()
+ {
+ return (int)Id;
+ }
+
+ /// <summary>Checks two <see cref="DataflowMessageHeader"/> instances for equality by ID.</summary>
+ /// <param name="left">A <see cref="DataflowMessageHeader"/> instance.</param>
+ /// <param name="right">A <see cref="DataflowMessageHeader"/> instance.</param>
+ /// <returns>True if the instances are equal. False otherwise.</returns>
+ public static bool operator ==(DataflowMessageHeader left, DataflowMessageHeader right)
+ {
+ return left.Id == right.Id;
+ }
+
+ /// <summary>Checks two <see cref="DataflowMessageHeader"/> instances for non-equality by ID.</summary>
+ /// <param name="left">A <see cref="DataflowMessageHeader"/> instance.</param>
+ /// <param name="right">A <see cref="DataflowMessageHeader"/> instance.</param>
+ /// <returns>True if the instances are not equal. False otherwise.</returns>
+ public static bool operator !=(DataflowMessageHeader left, DataflowMessageHeader right)
+ {
+ return left.Id != right.Id;
+ }
+ #endregion
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// DataflowMessageStatus.cs
+//
+//
+// Status about the propagation of a message.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Represents the status of a <see cref="DataflowMessageHeader"/> when passed between dataflow blocks.</summary>
+ public enum DataflowMessageStatus
+ {
+ /// <summary>
+ /// Indicates that the <see cref="ITargetBlock{TInput}"/> accepted the message. Once a target has accepted a message,
+ /// it is wholly owned by the target.
+ /// </summary>
+ Accepted = 0x0,
+
+ /// <summary>
+ /// Indicates that the <see cref="ITargetBlock{TInput}"/> declined the message. The <see cref="ISourceBlock{TOutput}"/> still owns the message.
+ /// </summary>
+ Declined = 0x1,
+
+ /// <summary>
+ /// Indicates that the <see cref="ITargetBlock{TInput}"/> postponed the message for potential consumption at a later time.
+ /// The <see cref="ISourceBlock{TOutput}"/> still owns the message.
+ /// </summary>
+ Postponed = 0x2,
+
+ /// <summary>
+ /// Indicates that the <see cref="ITargetBlock{TInput}"/> tried to accept the message from the <see cref="ISourceBlock{TOutput}"/>, but the
+ /// message was no longer available.
+ /// </summary>
+ NotAvailable = 0x3,
+
+ /// <summary>
+ /// Indicates that the <see cref="ITargetBlock{TInput}"/> declined the message. The <see cref="ISourceBlock{TOutput}"/> still owns the message.
+ /// Additionally, the <see cref="ITargetBlock{TInput}"/> will decline all future messages sent by the source.
+ /// </summary>
+ DecliningPermanently = 0x4
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// IDataflowBlock.cs
+//
+//
+// The base interface for all dataflow blocks.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Represents a dataflow block.</summary>
+ public interface IDataflowBlock
+ {
+ // IMPLEMENT IMPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task Completion { get; }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void Complete();
+
+ // IMPLEMENT EXPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void Fault(Exception exception);
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// IPropagatorBlock.cs
+//
+//
+// The base interface for all propagator blocks.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Represents a dataflow block that is both a target for data and a source of data.</summary>
+ /// <typeparam name="TInput">Specifies the type of data accepted by the <see cref="IPropagatorBlock{TInput,TOutput}"/>.</typeparam>
+ /// <typeparam name="TOutput">Specifies the type of data supplied by the <see cref="IPropagatorBlock{TInput,TOutput}"/>.</typeparam>
+ public interface IPropagatorBlock<in TInput, out TOutput> : ITargetBlock<TInput>, ISourceBlock<TOutput>
+ {
+ // No additional members beyond those inherited from ITargetBlock<TInput> and ISourceBlock<TOutput>
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// IReceivableSourceBlock.cs
+//
+//
+// The base interface for all source blocks.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Represents a dataflow block that supports receiving of messages without linking.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data supplied by the <see cref="IReceivableSourceBlock{TOutput}"/>.</typeparam>
+ public interface IReceivableSourceBlock<TOutput> : ISourceBlock<TOutput>
+ {
+ // IMPLEMENT IMPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ bool TryReceive(Predicate<TOutput> filter, out TOutput item);
+
+ // IMPLEMENT IMPLICITLY IF BLOCK SUPPORTS RECEIVING MORE THAN ONE ITEM, OTHERWISE EXPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ bool TryReceiveAll(out IList<TOutput> items);
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ISourceBlock.cs
+//
+//
+// The base interface for all source blocks.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Represents a dataflow block that is a source of data.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data supplied by the <see cref="ISourceBlock{TOutput}"/>.</typeparam>
+ public interface ISourceBlock<out TOutput> : IDataflowBlock
+ {
+ // IMPLEMENT IMPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ IDisposable LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions);
+
+ // IMPLEMENT EXPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
+ TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out Boolean messageConsumed);
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ Boolean ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target);
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target);
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ITargetBlock.cs
+//
+//
+// The base interface for all target blocks.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Represents a dataflow block that is a target for data.</summary>
+ /// <typeparam name="TInput">Specifies the type of data accepted by the <see cref="ITargetBlock{TInput}"/>.</typeparam>
+ public interface ITargetBlock<in TInput> : IDataflowBlock
+ {
+ // IMPLEMENT EXPLICITLY
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, Boolean consumeToAccept);
+ }
+}
--- /dev/null
+// 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); } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// BatchBlock.cs
+//
+//
+// A propagator block that groups individual messages into arrays of messages.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Security;
+using System.Threading.Tasks.Dataflow.Internal;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Provides a dataflow block that batches inputs into arrays.</summary>
+ /// <typeparam name="T">Specifies the type of data put into batches.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(BatchBlock<>.DebugView))]
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ public sealed class BatchBlock<T> : IPropagatorBlock<T, T[]>, IReceivableSourceBlock<T[]>, IDebuggerDisplay
+ {
+ /// <summary>The target half of this batch.</summary>
+ private readonly BatchBlockTargetCore _target;
+ /// <summary>The source half of this batch.</summary>
+ private readonly SourceCore<T[]> _source;
+
+ /// <summary>Initializes this <see cref="BatchBlock{T}"/> with the specified batch size.</summary>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ public BatchBlock(Int32 batchSize) :
+ this(batchSize, GroupingDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes this <see cref="BatchBlock{T}"/> with the specified batch size, declining option, and block options.</summary>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BatchBlock{T}"/>.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be no greater than the value of the BoundedCapacity option if a non-default value has been set.</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public BatchBlock(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.BoundedCapacity > 0 && dataflowBlockOptions.BoundedCapacity < batchSize) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_BatchSizeMustBeNoGreaterThanBoundedCapacity);
+ Contract.EndContractBlock();
+
+ // Ensure we have options that can't be changed by the caller
+ dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone();
+
+ // Initialize bounding actions
+ Action<ISourceBlock<T[]>, int> onItemsRemoved = null;
+ Func<ISourceBlock<T[]>, T[], IList<T[]>, int> itemCountingFunc = null;
+ if (dataflowBlockOptions.BoundedCapacity > 0)
+ {
+ onItemsRemoved = (owningSource, count) => ((BatchBlock<T>)owningSource)._target.OnItemsRemoved(count);
+ itemCountingFunc = (owningSource, singleOutputItem, multipleOutputItems) => BatchBlockTargetCore.CountItems(singleOutputItem, multipleOutputItems);
+ }
+
+ // Initialize source
+ _source = new SourceCore<T[]>(this, dataflowBlockOptions,
+ owningSource => ((BatchBlock<T>)owningSource)._target.Complete(exception: null, dropPendingMessages: true, releaseReservedMessages: false),
+ onItemsRemoved, itemCountingFunc);
+
+ // Initialize target
+ _target = new BatchBlockTargetCore(this, batchSize, batch => _source.AddMessage(batch), dataflowBlockOptions);
+
+ // When the target is done, let the source know it won't be getting any more data
+ _target.Completion.ContinueWith(delegate { _source.Complete(); },
+ 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 = ((BatchBlock<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, state => ((BatchBlockTargetCore)state).Complete(exception: null, dropPendingMessages: true, releaseReservedMessages: false), _target);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ public void Complete() { _target.Complete(exception: null, dropPendingMessages: false, releaseReservedMessages: 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, releaseReservedMessages: false);
+ }
+
+ /// <summary>
+ /// Triggers the <see cref="BatchBlock{T}"/> to initiate a batching operation even if the number
+ /// of currently queued or postponed items is less than the <see cref="BatchSize"/>.
+ /// </summary>
+ /// <remarks>
+ /// In greedy mode, a batch will be generated from queued items even if fewer exist than the batch size.
+ /// In non-greedy mode, a batch will be generated asynchronously from postponed items even if
+ /// fewer than the batch size can be consumed.
+ /// </remarks>
+ public void TriggerBatch() { _target.TriggerBatch(); }
+
+ /// <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 bool TryReceiveAll(out IList<T[]> items) { return _source.TryReceiveAll(out items); }
+
+ /// <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/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { return _source.Completion; } }
+
+ /// <summary>Gets the size of the batches generated by this <see cref="BatchBlock{T}"/>.</summary>
+ /// <remarks>
+ /// If the number of items provided to the block is not evenly divisible by the batch size provided
+ /// to the block's constructor, the block's final batch may contain fewer than the requested number of items.
+ /// </remarks>
+ public Int32 BatchSize { get { return _target.BatchSize; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source, Boolean consumeToAccept)
+ {
+ return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept);
+ }
+
+ /// <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>Gets the number of messages waiting to be offered. 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}, BatchSize={1}, OutputCount={2}",
+ Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
+ BatchSize,
+ 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 Batch.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The batch block being viewed.</summary>
+ private BatchBlock<T> _batchBlock;
+ /// <summary>The target half being viewed.</summary>
+ private readonly BatchBlockTargetCore.DebuggingInformation _targetDebuggingInformation;
+ /// <summary>The source half of the block being viewed.</summary>
+ private readonly SourceCore<T[]>.DebuggingInformation _sourceDebuggingInformation;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="batchBlock">The batch being viewed.</param>
+ public DebugView(BatchBlock<T> batchBlock)
+ {
+ Contract.Requires(batchBlock != null, "Need a block with which to construct the debug view");
+ _batchBlock = batchBlock;
+ _targetDebuggingInformation = batchBlock._target.GetDebuggingInformation();
+ _sourceDebuggingInformation = batchBlock._source.GetDebuggingInformation();
+ }
+
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ public IEnumerable<T> InputQueue { get { return _targetDebuggingInformation.InputQueue; } }
+ /// <summary>Gets the messages waiting to be received.</summary>
+ public IEnumerable<T[]> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } }
+ /// <summary>Gets the number of batches that have been completed.</summary>
+ public long BatchesCompleted { get { return _targetDebuggingInformation.NumberOfBatchesCompleted; } }
+
+ /// <summary>Gets the task being used for input processing.</summary>
+ public Task TaskForInputProcessing { get { return _targetDebuggingInformation.TaskForInputProcessing; } }
+ /// <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 GroupingDataflowBlockOptions DataflowBlockOptions { get { return _targetDebuggingInformation.DataflowBlockOptions; } }
+ /// <summary>Gets the size of batches generated by the block.</summary>
+ public int BatchSize { get { return _batchBlock.BatchSize; } }
+ /// <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(_batchBlock); } }
+
+ /// <summary>Gets the messages postponed by this batch.</summary>
+ public QueuedMap<ISourceBlock<T>, DataflowMessageHeader> PostponedMessages { get { return _targetDebuggingInformation.PostponedMessages; } }
+ /// <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; } }
+ }
+
+ /// <summary>Provides the core target implementation for a Batch.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ private sealed class BatchBlockTargetCore
+ {
+ /// <summary>The messages in this target.</summary>
+ private readonly Queue<T> _messages = new Queue<T>();
+ /// <summary>A task representing the completion of the block.</summary>
+ private readonly TaskCompletionSource<VoidResult> _completionTask = new TaskCompletionSource<VoidResult>();
+
+ /// <summary>Gets the object used as the incoming lock.</summary>
+ private object IncomingLock { get { return _completionTask; } }
+
+ /// <summary>The target that owns this target core.</summary>
+ private readonly BatchBlock<T> _owningBatch;
+ /// <summary>The batch size.</summary>
+ private readonly int _batchSize;
+ /// <summary>State used when in non-greedy mode.</summary>
+ private readonly NonGreedyState _nonGreedyState;
+ /// <summary>Bounding state for when the block is executing in bounded mode.</summary>
+ private readonly BoundingState _boundingState;
+ /// <summary>The options associated with this block.</summary>
+ private readonly GroupingDataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>The action invoked with a completed batch.</summary>
+ private readonly Action<T[]> _batchCompletedAction;
+
+ /// <summary>Whether to stop accepting new messages.</summary>
+ private bool _decliningPermanently;
+ /// <summary>Whether we've completed at least one batch.</summary>
+ private long _batchesCompleted;
+ /// <summary>Whether someone has reserved the right to call CompleteBlockOncePossible.</summary>
+ private bool _completionReserved;
+
+ /// <summary>State used only when in non-greedy mode.</summary>
+ private sealed class NonGreedyState
+ {
+ /// <summary>Collection of postponed messages.</summary>
+ internal readonly QueuedMap<ISourceBlock<T>, DataflowMessageHeader> PostponedMessages;
+ /// <summary>A temporary array used to store data retrieved from PostponedMessages.</summary>
+ internal readonly KeyValuePair<ISourceBlock<T>, DataflowMessageHeader>[] PostponedMessagesTemp;
+ /// <summary>A temporary list used in non-greedy mode when consuming postponed messages to store successfully reserved messages.</summary>
+ internal readonly List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>> ReservedSourcesTemp;
+ /// <summary>Whether the next batching operation should accept fewer than BatchSize items.</summary>
+ /// <remarks>This value may be read not under a lock, but it must only be written to protected by the IncomingLock.</remarks>
+ internal bool AcceptFewerThanBatchSize;
+ /// <summary>The task used to process messages.</summary>
+ internal Task TaskForInputProcessing;
+
+ /// <summary>Initializes the NonGreedyState.</summary>
+ /// <param name="batchSize">The batch size used by the BatchBlock.</param>
+ internal NonGreedyState(int batchSize)
+ {
+ // A non-greedy batch requires at least batchSize sources to be successful.
+ // Thus, we initialize our collections to be able to store at least that many elements
+ // in order to avoid unnecessary allocations below that point.
+ Contract.Requires(batchSize > 0, "A positive batch size is required");
+ PostponedMessages = new QueuedMap<ISourceBlock<T>, DataflowMessageHeader>(batchSize);
+ PostponedMessagesTemp = new KeyValuePair<ISourceBlock<T>, DataflowMessageHeader>[batchSize];
+ ReservedSourcesTemp = new List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>>(batchSize);
+ }
+ }
+
+ /// <summary>Initializes this target core with the specified configuration.</summary>
+ /// <param name="owningBatch">The owning batch target.</param>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <param name="batchCompletedAction">The delegate to invoke when a batch is completed.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BatchBlock{T}"/>. Assumed to be immutable.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ internal BatchBlockTargetCore(BatchBlock<T> owningBatch, Int32 batchSize, Action<T[]> batchCompletedAction, GroupingDataflowBlockOptions dataflowBlockOptions)
+ {
+ Contract.Requires(owningBatch != null, "This batch target core must be associated with a batch block.");
+ Contract.Requires(batchSize >= 1, "Batch sizes must be positive.");
+ Contract.Requires(batchCompletedAction != null, "Completion action must be specified.");
+ Contract.Requires(dataflowBlockOptions != null, "Options required to configure the block.");
+
+ // Store arguments
+ _owningBatch = owningBatch;
+ _batchSize = batchSize;
+ _batchCompletedAction = batchCompletedAction;
+ _dataflowBlockOptions = dataflowBlockOptions;
+
+ // We'll be using _nonGreedyState even if we are greedy with bounding
+ bool boundingEnabled = dataflowBlockOptions.BoundedCapacity > 0;
+ if (!_dataflowBlockOptions.Greedy || boundingEnabled) _nonGreedyState = new NonGreedyState(batchSize);
+ if (boundingEnabled) _boundingState = new BoundingState(dataflowBlockOptions.BoundedCapacity);
+ }
+
+ /// <summary>
+ /// Triggers a batching operation even if the number of currently queued or postponed items is less than the <see cref="BatchSize"/>.
+ /// </summary>
+ internal void TriggerBatch()
+ {
+ lock (IncomingLock)
+ {
+ // If we shouldn't be doing any more work, bail. Otherwise, note that we're willing to
+ // accept fewer items in the next batching operation, and ensure processing is kicked off.
+ if (!_decliningPermanently && !_dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ {
+ if (_nonGreedyState == null)
+ {
+ MakeBatchIfPossible(evenIfFewerThanBatchSize: true);
+ }
+ else
+ {
+ _nonGreedyState.AcceptFewerThanBatchSize = true;
+ ProcessAsyncIfNecessary();
+ }
+ }
+ CompleteBlockIfPossible();
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ internal DataflowMessageStatus 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 shouldn't be accepting more messages, don't.
+ if (_decliningPermanently)
+ {
+ CompleteBlockIfPossible();
+ return DataflowMessageStatus.DecliningPermanently;
+ }
+
+ // We can directly accept the message if:
+ // 1) we are being greedy AND we are not bounding, OR
+ // 2) we are being greedy AND 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 (_dataflowBlockOptions.Greedy &&
+ (_boundingState == null
+ ||
+ (_boundingState.CountIsLessThanBound && _nonGreedyState.PostponedMessages.Count == 0 && _nonGreedyState.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, _owningBatch, out consumed);
+ if (!consumed) return DataflowMessageStatus.NotAvailable;
+ }
+
+ // Once consumed, enqueue it.
+ _messages.Enqueue(messageValue);
+ if (_boundingState != null) _boundingState.CurrentCount += 1; // track this new item against our bound
+
+ // Now start declining if the number of batches we've already made plus
+ // the number we can make from data already enqueued meets our quota.
+ if (!_decliningPermanently &&
+ (_batchesCompleted + (_messages.Count / _batchSize)) >= _dataflowBlockOptions.ActualMaxNumberOfGroups)
+ {
+ _decliningPermanently = true;
+ }
+
+ // Now that we have a message, see if we can make forward progress.
+ MakeBatchIfPossible(evenIfFewerThanBatchSize: false);
+
+ CompleteBlockIfPossible();
+ return DataflowMessageStatus.Accepted;
+ }
+ // Otherwise, we try to postpone if a source was provided
+ else if (source != null)
+ {
+ Debug.Assert(_nonGreedyState != null, "_nonGreedyState must have been initialized during construction in non-greedy mode.");
+
+ // We always postpone using _nonGreedyState even if we are being greedy with bounding
+ _nonGreedyState.PostponedMessages.Push(source, messageHeader);
+
+ // In non-greedy mode, we need to see if batch could be completed
+ if (!_dataflowBlockOptions.Greedy) ProcessAsyncIfNecessary();
+
+ return DataflowMessageStatus.Postponed;
+ }
+ // We can't do anything else about this message
+ return DataflowMessageStatus.Declined;
+ }
+ }
+
+ /// <summary>Completes/faults the block.
+ /// In general, it is not safe to pass releaseReservedMessages:true, because releasing of reserved messages
+ /// is done without taking a lock. We pass releaseReservedMessages:true only when an exception has been
+ /// caught inside the message processing loop which is a single instance at any given moment.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal void Complete(Exception exception, bool dropPendingMessages, bool releaseReservedMessages, bool revertProcessingState = false)
+ {
+ // Ensure that no new messages may be added
+ lock (IncomingLock)
+ {
+ // Faulting from outside is allowed until we start declining permanently.
+ // Faulting from inside is allowed at any time.
+ if (exception != null && (!_decliningPermanently || releaseReservedMessages))
+ {
+ // Record the exception in the source.
+ // The source, which exposes its Completion to the public will take this
+ // into account and will complete in Faulted state.
+ _owningBatch._source.AddException(exception);
+ }
+
+ // Drop pending messages if requested
+ if (dropPendingMessages) _messages.Clear();
+ }
+
+ // Release reserved messages if requested.
+ // This must be done from outside the lock.
+ if (releaseReservedMessages)
+ {
+ try { ReleaseReservedMessages(throwOnFirstException: false); }
+ catch (Exception e) { _owningBatch._source.AddException(e); }
+ }
+
+ // Triggering completion requires the lock
+ lock (IncomingLock)
+ {
+ // Revert the dirty processing state if requested
+ if (revertProcessingState)
+ {
+ Debug.Assert(_nonGreedyState != null && _nonGreedyState.TaskForInputProcessing != null,
+ "The processing state must be dirty when revertProcessingState==true.");
+ _nonGreedyState.TaskForInputProcessing = null;
+ }
+
+ // Trigger completion
+ _decliningPermanently = true;
+ CompleteBlockIfPossible();
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ internal Task Completion { get { return _completionTask.Task; } }
+
+ /// <summary>Gets the size of the batches generated by this <see cref="BatchBlock{T}"/>.</summary>
+ internal Int32 BatchSize { get { return _batchSize; } }
+
+ /// <summary>Gets whether the target has had cancellation requested or an exception has occurred.</summary>
+ private bool CanceledOrFaulted
+ {
+ get
+ {
+ return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || _owningBatch._source.HasExceptions;
+ }
+ }
+
+ /// <summary>Returns the available capacity to bring in postponed items. The exact values above _batchSize don't matter.</summary>
+ private int BoundedCapacityAvailable
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ return _boundingState != null ?
+ _dataflowBlockOptions.BoundedCapacity - _boundingState.CurrentCount :
+ _batchSize;
+ }
+ }
+
+ /// <summary>Completes the block once all completion conditions are met.</summary>
+ private void CompleteBlockIfPossible()
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (!_completionReserved)
+ {
+ bool currentlyProcessing = _nonGreedyState != null && _nonGreedyState.TaskForInputProcessing != null;
+ bool completedAllDesiredBatches = _batchesCompleted >= _dataflowBlockOptions.ActualMaxNumberOfGroups;
+ bool noMoreMessages = _decliningPermanently && _messages.Count < _batchSize;
+
+ bool complete = !currentlyProcessing && (completedAllDesiredBatches || noMoreMessages || CanceledOrFaulted);
+ if (complete)
+ {
+ _completionReserved = true;
+
+ // Make sure the target is declining
+ _decliningPermanently = true;
+
+ // If we still have straggling items remaining, make them into their own batch even though there are fewer than batchSize
+ if (_messages.Count > 0) MakeBatchIfPossible(evenIfFewerThanBatchSize: true);
+
+ // We need to complete the block, but we may have arrived here from an external
+ // call to the block. To avoid running arbitrary code in the form of
+ // completion task continuations in that case, do it in a separate task.
+ Task.Factory.StartNew(thisTargetCore =>
+ {
+ var targetCore = (BatchBlockTargetCore)thisTargetCore;
+
+ // Release any postponed messages
+ List<Exception> exceptions = null;
+ if (targetCore._nonGreedyState != null)
+ {
+ // Note: No locks should be held at this point
+ Common.ReleaseAllPostponedMessages(targetCore._owningBatch,
+ targetCore._nonGreedyState.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.
+ targetCore._owningBatch._source.AddExceptions(exceptions);
+ }
+
+ // Target's completion task is only available internally with the sole purpose
+ // of releasing the task that completes the parent. Hence the actual reason
+ // for completing this task doesn't matter.
+ targetCore._completionTask.TrySetResult(default(VoidResult));
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets whether we should launch further synchronous or asynchronous processing
+ /// to create batches.
+ /// </summary>
+ private bool BatchesNeedProcessing
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ // If we're currently processing asynchronously, let that async task
+ // handle all work; nothing more to do here. If we're not currently processing
+ // but cancellation has been requested, don't do more work either.
+ bool completedAllDesiredBatches = _batchesCompleted >= _dataflowBlockOptions.ActualMaxNumberOfGroups;
+ bool currentlyProcessing = _nonGreedyState != null && _nonGreedyState.TaskForInputProcessing != null;
+ if (completedAllDesiredBatches || currentlyProcessing || CanceledOrFaulted) return false;
+
+ // Now, if it's possible to create a batch from queued items or if there are enough
+ // postponed items to attempt a batch, batches need processing.
+ int neededMessageCountToCompleteBatch = _batchSize - _messages.Count;
+ int boundedCapacityAvailable = BoundedCapacityAvailable;
+
+ // We have items queued up sufficient to make up a batch
+ if (neededMessageCountToCompleteBatch <= 0) return true;
+
+ if (_nonGreedyState != null)
+ {
+ // We can make a triggered batch using postponed messages
+ if (_nonGreedyState.AcceptFewerThanBatchSize &&
+ (_messages.Count > 0 || (_nonGreedyState.PostponedMessages.Count > 0 && boundedCapacityAvailable > 0)))
+ return true;
+
+ if (_dataflowBlockOptions.Greedy)
+ {
+ // We are in greedy mode and we have postponed messages.
+ // (In greedy mode we only postpone due to lack of bounding capacity.)
+ // And now we have capacity to consume some postponed messages.
+ // (In greedy mode we can/should consume as many postponed messages as we can even
+ // if those messages are insufficient to make up a batch.)
+ if (_nonGreedyState.PostponedMessages.Count > 0 && boundedCapacityAvailable > 0) return true;
+ }
+ else
+ {
+ // We are in non-greedy mode and we have enough postponed messages and bounding capacity to make a full batch
+ if (_nonGreedyState.PostponedMessages.Count >= neededMessageCountToCompleteBatch &&
+ boundedCapacityAvailable >= neededMessageCountToCompleteBatch)
+ return true;
+ }
+ }
+
+ // There is no other reason to kick off a processing task
+ return false;
+ }
+ }
+
+ /// <summary>Called when new messages are available to be processed.</summary>
+ /// <param name="isReplacementReplica">Whether this call is the continuation of a previous message loop.</param>
+ private void ProcessAsyncIfNecessary(bool isReplacementReplica = false)
+ {
+ Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (BatchesNeedProcessing)
+ {
+ ProcessAsyncIfNecessary_Slow(isReplacementReplica);
+ }
+ }
+
+ /// <summary>
+ /// Slow path for ProcessAsyncIfNecessary.
+ /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined.
+ /// </summary>
+ private void ProcessAsyncIfNecessary_Slow(bool isReplacementReplica)
+ {
+ Contract.Requires(BatchesNeedProcessing, "There must be a batch that needs processing.");
+
+ // Create task and store into _taskForInputProcessing prior to scheduling the task
+ // so that _taskForInputProcessing will be visibly set in the task loop.
+ _nonGreedyState.TaskForInputProcessing = new Task(thisBatchTarget => ((BatchBlockTargetCore)thisBatchTarget).ProcessMessagesLoopCore(), this,
+ Common.GetCreationOptionsForTask(isReplacementReplica));
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ _owningBatch, _nonGreedyState.TaskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages,
+ _messages.Count + (_nonGreedyState != null ? _nonGreedyState.PostponedMessages.Count : 0));
+ }
+#endif
+
+ // Start the task handling scheduling exceptions
+ Exception exception = Common.StartTaskSafe(_nonGreedyState.TaskForInputProcessing, _dataflowBlockOptions.TaskScheduler);
+ if (exception != null)
+ {
+ // Get out from under currently held locks. Complete re-acquires the locks it needs.
+ Task.Factory.StartNew(exc => Complete(exception: (Exception)exc, dropPendingMessages: true, releaseReservedMessages: true, revertProcessingState: true),
+ exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+
+
+ /// <summary>Task body used to process messages.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void ProcessMessagesLoopCore()
+ {
+ Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+ try
+ {
+ int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask;
+ int timesThroughLoop = 0;
+ bool madeProgress;
+ do
+ {
+ // Determine whether a batch has been forced/triggered.
+ // (If the value is read as false and is set to true immediately afterwards,
+ // we'll simply force the next time around. The only code that can
+ // set the value to false is this function, after reading a true value.)
+ bool triggered = Volatile.Read(ref _nonGreedyState.AcceptFewerThanBatchSize);
+
+ // Retrieve postponed items:
+ // In non-greedy mode: Reserve + Consume
+ // In greedy bounded mode: Consume (without a prior reservation)
+ if (!_dataflowBlockOptions.Greedy) RetrievePostponedItemsNonGreedy(allowFewerThanBatchSize: triggered);
+ else RetrievePostponedItemsGreedyBounded(allowFewerThanBatchSize: triggered);
+
+ // Try to make a batch if there are enough buffered messages
+ lock (IncomingLock)
+ {
+ madeProgress = MakeBatchIfPossible(evenIfFewerThanBatchSize: triggered);
+
+ // Reset the trigger flag if:
+ // - We made a batch, regardless of whether it came due to a trigger or not.
+ // - We tried to make a batch due to a trigger, but were unable to, which
+ // could happen if we're unable to consume any of the postponed messages.
+ if (madeProgress || triggered) _nonGreedyState.AcceptFewerThanBatchSize = false;
+ }
+
+ timesThroughLoop++;
+ } while (madeProgress && timesThroughLoop < maxMessagesPerTask);
+ }
+ catch (Exception exc)
+ {
+ Complete(exc, dropPendingMessages: false, releaseReservedMessages: true);
+ }
+ finally
+ {
+ lock (IncomingLock)
+ {
+ // We're no longer processing, so null out the processing task
+ _nonGreedyState.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.
+ ProcessAsyncIfNecessary(isReplacementReplica: true);
+
+ // If, however, we stopped because we ran out of work to do and we
+ // know we'll never get more, then complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <summary>Create a batch from the available items.</summary>
+ /// <param name="evenIfFewerThanBatchSize">
+ /// Whether to make a batch even if there are fewer than BatchSize items available.
+ /// </param>
+ /// <returns>true if a batch was created and published; otherwise, false.</returns>
+ private bool MakeBatchIfPossible(bool evenIfFewerThanBatchSize)
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ // Is a full batch available?
+ bool fullBatch = _messages.Count >= _batchSize;
+
+ // If so, or if it's ok to make a batch with fewer than batchSize, make one.
+ if (fullBatch || (evenIfFewerThanBatchSize && _messages.Count > 0))
+ {
+ var newBatch = new T[fullBatch ? _batchSize : _messages.Count];
+ for (int i = 0; i < newBatch.Length; i++) newBatch[i] = _messages.Dequeue();
+ _batchCompletedAction(newBatch);
+ _batchesCompleted++;
+ if (_batchesCompleted >= _dataflowBlockOptions.ActualMaxNumberOfGroups) _decliningPermanently = true;
+ return true;
+ }
+ // No batch could be created
+ else return false;
+ }
+
+ /// <summary>Retrieves postponed items in non-greedy mode if we have enough to make a batch.</summary>
+ /// <remarks>Whether we'll accept consuming fewer elements than the defined batch size.</remarks>
+ private void RetrievePostponedItemsNonGreedy(bool allowFewerThanBatchSize)
+ {
+ Contract.Requires(!_dataflowBlockOptions.Greedy, "This method may only be used in non-greedy mode.");
+ Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // Shortcuts just to keep the code cleaner
+ QueuedMap<ISourceBlock<T>, DataflowMessageHeader> postponed = _nonGreedyState.PostponedMessages;
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader>[] postponedTemp = _nonGreedyState.PostponedMessagesTemp;
+ List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader,T>>> reserved = _nonGreedyState.ReservedSourcesTemp;
+
+ // Clear the temporary buffer. This is safe to do without a lock because
+ // it is only accessed by the serial message loop.
+ reserved.Clear();
+
+ int poppedInitially;
+ int boundedCapacityAvailable;
+ lock (IncomingLock)
+ {
+ // The queue must be empty between batches in non-greedy mode
+ Debug.Assert(_messages.Count == 0, "The queue must be empty between batches in non-greedy mode");
+
+ // If there are not enough postponed items (or if we're not allowing consumption), there's nothing more to be done
+ boundedCapacityAvailable = BoundedCapacityAvailable;
+ if (_decliningPermanently ||
+ postponed.Count == 0 ||
+ boundedCapacityAvailable <= 0 ||
+ (!allowFewerThanBatchSize && (postponed.Count < _batchSize || boundedCapacityAvailable < _batchSize)))
+ return;
+
+ // Grab an initial batch of postponed messages.
+ poppedInitially = postponed.PopRange(postponedTemp, 0, _batchSize);
+ Debug.Assert(allowFewerThanBatchSize ? poppedInitially > 0 : poppedInitially == _batchSize,
+ "We received fewer than we expected based on the previous check.");
+ } // Release the lock. We must not hold it while calling Reserve/Consume/Release.
+
+ // Try to reserve the initial batch of messages.
+ for (int i = 0; i < poppedInitially; i++)
+ {
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> sourceAndMessage = postponedTemp[i];
+ if (sourceAndMessage.Key.ReserveMessage(sourceAndMessage.Value, _owningBatch))
+ {
+ var reservedMessage = new KeyValuePair<DataflowMessageHeader, T>(sourceAndMessage.Value, default(T));
+ var reservedSourceAndMessage = new KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>(sourceAndMessage.Key, reservedMessage);
+ reserved.Add(reservedSourceAndMessage);
+ }
+ }
+ Array.Clear(postponedTemp, 0, postponedTemp.Length); // clear out the temp array so as not to hold onto messages too long
+
+ // If we didn't reserve enough to make a batch, start picking off postponed messages
+ // one by one until we either have enough reserved or we run out of messages
+ while (reserved.Count < _batchSize)
+ {
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> sourceAndMessage;
+ lock (IncomingLock)
+ {
+ if (!postponed.TryPop(out sourceAndMessage)) break;
+ } // Release the lock. We must not hold it while calling Reserve/Consume/Release.
+ if (sourceAndMessage.Key.ReserveMessage(sourceAndMessage.Value, _owningBatch))
+ {
+ var reservedMessage = new KeyValuePair<DataflowMessageHeader, T>(sourceAndMessage.Value, default(T));
+ var reservedSourceAndMessage = new KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>(sourceAndMessage.Key, reservedMessage);
+ reserved.Add(reservedSourceAndMessage);
+ }
+ }
+
+ Debug.Assert(reserved.Count <= _batchSize, "Expected the number of reserved sources to be <= the number needed for a batch.");
+
+ // We've now reserved what we can. Either consume them all or release them all.
+ if (reserved.Count > 0)
+ {
+ // TriggerBatch adds a complication here. It's possible that while we've been reserving
+ // messages, Post has been used to queue up a bunch of messages to the batch,
+ // and that if the batch has a max group count and enough messages were posted,
+ // we could now be declining. In that case, if we don't specially handle the situation,
+ // we could consume messages that we won't be able to turn into a batch, since MaxNumberOfGroups
+ // implies the block will only ever output a maximum number of batches. To handle this,
+ // we start declining before consuming, now that we know we'll have enough to form a batch.
+ // (If an exception occurs after we do this, we'll be shutting down the block anyway.)
+ // This is also why we still reserve/consume rather than just consume in forced mode,
+ // so that we only consume if we're able to turn what we consume into a batch.
+ bool shouldProceedToConsume = true;
+ if (allowFewerThanBatchSize)
+ {
+ lock (IncomingLock)
+ {
+ if (!_decliningPermanently &&
+ (_batchesCompleted + 1) >= _dataflowBlockOptions.ActualMaxNumberOfGroups)
+ // Note that this logic differs from the other location where we do a similar check.
+ // Here we want to know whether we're one shy of meeting our quota, because we'll accept
+ // any size batch. Elsewhere, we need to know whether we have the right number of messages
+ // queued up.
+ {
+ shouldProceedToConsume = !_decliningPermanently;
+ _decliningPermanently = true;
+ }
+ }
+ }
+
+ if (shouldProceedToConsume && (allowFewerThanBatchSize || reserved.Count == _batchSize))
+ {
+ ConsumeReservedMessagesNonGreedy();
+ }
+ else
+ {
+ ReleaseReservedMessages(throwOnFirstException: true);
+ }
+ }
+
+ // Clear out the reserved list, so as not to hold onto values longer than necessary.
+ // We don't do this in case of failure, because the higher-level exception handler
+ // accesses the list to try to release reservations.
+ reserved.Clear();
+ }
+
+ /// <summary>Retrieves postponed items in greedy bounded mode.</summary>
+ /// <remarks>Whether we'll accept consuming fewer elements than the defined batch size.</remarks>
+ private void RetrievePostponedItemsGreedyBounded(bool allowFewerThanBatchSize)
+ {
+ Contract.Requires(_dataflowBlockOptions.Greedy, "This method may only be used in greedy mode.");
+ Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Contract.Requires(_boundingState != null, "Bounding state is required when in bounded mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // Shortcuts just to keep the code cleaner
+ QueuedMap<ISourceBlock<T>, DataflowMessageHeader> postponed = _nonGreedyState.PostponedMessages;
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader>[] postponedTemp = _nonGreedyState.PostponedMessagesTemp;
+ List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>> reserved = _nonGreedyState.ReservedSourcesTemp;
+
+ // Clear the temporary buffer. This is safe to do without a lock because
+ // it is only accessed by the serial message loop.
+ reserved.Clear();
+
+ int poppedInitially;
+ int boundedCapacityAvailable;
+ int itemCountNeededToCompleteBatch;
+ lock (IncomingLock)
+ {
+ // If there are not enough postponed items (or if we're not allowing consumption), there's nothing more to be done
+ boundedCapacityAvailable = BoundedCapacityAvailable;
+ itemCountNeededToCompleteBatch = _batchSize - _messages.Count;
+ if (_decliningPermanently ||
+ postponed.Count == 0 ||
+ boundedCapacityAvailable <= 0)
+ return;
+
+ // Grab an initial batch of postponed messages.
+ if (boundedCapacityAvailable < itemCountNeededToCompleteBatch) itemCountNeededToCompleteBatch = boundedCapacityAvailable;
+ poppedInitially = postponed.PopRange(postponedTemp, 0, itemCountNeededToCompleteBatch);
+ Debug.Assert(poppedInitially > 0, "We received fewer than we expected based on the previous check.");
+ } // Release the lock. We must not hold it while calling Reserve/Consume/Release.
+
+ // Treat popped messages as reserved.
+ // We don't have to formally reserve because we are in greedy mode.
+ for (int i = 0; i < poppedInitially; i++)
+ {
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> sourceAndMessage = postponedTemp[i];
+ var reservedMessage = new KeyValuePair<DataflowMessageHeader, T>(sourceAndMessage.Value, default(T));
+ var reservedSourceAndMessage = new KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>(sourceAndMessage.Key, reservedMessage);
+ reserved.Add(reservedSourceAndMessage);
+ }
+ Array.Clear(postponedTemp, 0, postponedTemp.Length); // clear out the temp array so as not to hold onto messages too long
+
+ // If we didn't reserve enough to make a batch, start picking off postponed messages
+ // one by one until we either have enough reserved or we run out of messages
+ while (reserved.Count < itemCountNeededToCompleteBatch)
+ {
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> sourceAndMessage;
+ lock (IncomingLock)
+ {
+ if (!postponed.TryPop(out sourceAndMessage)) break;
+ } // Release the lock. We must not hold it while calling Reserve/Consume/Release.
+
+ var reservedMessage = new KeyValuePair<DataflowMessageHeader, T>(sourceAndMessage.Value, default(T));
+ var reservedSourceAndMessage = new KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>(sourceAndMessage.Key, reservedMessage);
+ reserved.Add(reservedSourceAndMessage);
+ }
+
+ Debug.Assert(reserved.Count <= itemCountNeededToCompleteBatch, "Expected the number of reserved sources to be <= the number needed for a batch.");
+
+ // We've gotten as many postponed messages as we can. Try to consume them.
+ if (reserved.Count > 0)
+ {
+ // TriggerBatch adds a complication here. It's possible that while we've been reserving
+ // messages, Post has been used to queue up a bunch of messages to the batch,
+ // and that if the batch has a max group count and enough messages were posted,
+ // we could now be declining. In that case, if we don't specially handle the situation,
+ // we could consume messages that we won't be able to turn into a batch, since MaxNumberOfGroups
+ // implies the block will only ever output a maximum number of batches. To handle this,
+ // we start declining before consuming, now that we know we'll have enough to form a batch.
+ // (If an exception occurs after we do this, we'll be shutting down the block anyway.)
+ // This is also why we still reserve/consume rather than just consume in forced mode,
+ // so that we only consume if we're able to turn what we consume into a batch.
+ bool shouldProceedToConsume = true;
+ if (allowFewerThanBatchSize)
+ {
+ lock (IncomingLock)
+ {
+ if (!_decliningPermanently &&
+ (_batchesCompleted + 1) >= _dataflowBlockOptions.ActualMaxNumberOfGroups)
+ // Note that this logic differs from the other location where we do a similar check.
+ // Here we want to know whether we're one shy of meeting our quota, because we'll accept
+ // any size batch. Elsewhere, we need to know whether we have the right number of messages
+ // queued up.
+ {
+ shouldProceedToConsume = !_decliningPermanently;
+ _decliningPermanently = true;
+ }
+ }
+ }
+
+ if (shouldProceedToConsume)
+ {
+ ConsumeReservedMessagesGreedyBounded();
+ }
+ }
+
+ // Clear out the reserved list, so as not to hold onto values longer than necessary.
+ // We don't do this in case of failure, because the higher-level exception handler
+ // accesses the list to try to release reservations.
+ reserved.Clear();
+ }
+
+ /// <summary>
+ /// Consumes all of the reserved messages stored in the non-greedy state's temporary reserved source list.
+ /// </summary>
+ private void ConsumeReservedMessagesNonGreedy()
+ {
+ Contract.Requires(!_dataflowBlockOptions.Greedy, "This method may only be used in non-greedy mode.");
+ Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Contract.Requires(_nonGreedyState.ReservedSourcesTemp != null, "ReservedSourcesTemp should have been initialized.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // Consume the reserved items and store the data.
+ List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>> reserved = _nonGreedyState.ReservedSourcesTemp;
+ for (int i = 0; i < reserved.Count; i++)
+ {
+ // We can only store the data into _messages while holding the IncomingLock, we
+ // don't want to allocate extra objects for each batch, and we don't want to
+ // take and release the lock for each individual item... but we do need to use
+ // the consumed message rather than the initial one. To handle this, because KeyValuePair is immutable,
+ // we store a new KVP with the newly consumed message back into the temp list, so that we can
+ // then enumerate the temp list en mass while taking the lock once afterwards.
+ KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>> sourceAndMessage = reserved[i];
+ reserved[i] = default(KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>); // in case of exception from ConsumeMessage
+ bool consumed;
+ T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value.Key, _owningBatch, out consumed);
+ if (!consumed)
+ {
+ // The protocol broke down, so throw an exception, as this is fatal. Before doing so, though,
+ // null out all of the messages we've already consumed, as a higher-level event handler
+ // should try to release everything in the reserved list.
+ for (int prev = 0; prev < i; prev++) reserved[prev] = default(KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>);
+ throw new InvalidOperationException(SR.InvalidOperation_FailedToConsumeReservedMessage);
+ }
+
+ var consumedMessage = new KeyValuePair<DataflowMessageHeader, T>(sourceAndMessage.Value.Key, consumedValue);
+ var consumedSourceAndMessage = new KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>(sourceAndMessage.Key, consumedMessage);
+ reserved[i] = consumedSourceAndMessage;
+ }
+ lock (IncomingLock)
+ {
+ // Increment the bounding count with the number of consumed messages
+ if (_boundingState != null) _boundingState.CurrentCount += reserved.Count;
+
+ // Enqueue the consumed mesasages
+ foreach (KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>> sourceAndMessage in reserved)
+ {
+ _messages.Enqueue(sourceAndMessage.Value.Value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Consumes all of the reserved messages stored in the non-greedy state's temporary reserved source list.
+ /// </summary>
+ private void ConsumeReservedMessagesGreedyBounded()
+ {
+ Contract.Requires(_dataflowBlockOptions.Greedy, "This method may only be used in greedy mode.");
+ Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Contract.Requires(_nonGreedyState.ReservedSourcesTemp != null, "ReservedSourcesTemp should have been initialized.");
+ Contract.Requires(_boundingState != null, "Bounded state is required for bounded mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // Consume the reserved items and store the data.
+ int consumedCount = 0;
+ List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>> reserved = _nonGreedyState.ReservedSourcesTemp;
+ for (int i = 0; i < reserved.Count; i++)
+ {
+ // We can only store the data into _messages while holding the IncomingLock, we
+ // don't want to allocate extra objects for each batch, and we don't want to
+ // take and release the lock for each individual item... but we do need to use
+ // the consumed message rather than the initial one. To handle this, because KeyValuePair is immutable,
+ // we store a new KVP with the newly consumed message back into the temp list, so that we can
+ // then enumerate the temp list en mass while taking the lock once afterwards.
+ KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>> sourceAndMessage = reserved[i];
+ reserved[i] = default(KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>); // in case of exception from ConsumeMessage
+ bool consumed;
+ T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value.Key, _owningBatch, out consumed);
+ if (consumed)
+ {
+ var consumedMessage = new KeyValuePair<DataflowMessageHeader, T>(sourceAndMessage.Value.Key, consumedValue);
+ var consumedSourceAndMessage = new KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>(sourceAndMessage.Key, consumedMessage);
+ reserved[i] = consumedSourceAndMessage;
+
+ // Keep track of the actually consumed messages
+ consumedCount++;
+ }
+ }
+ lock (IncomingLock)
+ {
+ // Increment the bounding count with the number of consumed messages
+ if (_boundingState != null) _boundingState.CurrentCount += consumedCount;
+
+ // Enqueue the consumed mesasages
+ foreach (KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>> sourceAndMessage in reserved)
+ {
+ // If we didn't consume this message, the KeyValuePai will be default, i.e. the source will be null
+ if (sourceAndMessage.Key != null) _messages.Enqueue(sourceAndMessage.Value.Value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Releases all of the reserved messages stored in the non-greedy state's temporary reserved source list.
+ /// </summary>
+ /// <param name="throwOnFirstException">
+ /// Whether to allow an exception from a release to propagate immediately,
+ /// or to delay propagation until all releases have been attempted.
+ /// </param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal void ReleaseReservedMessages(bool throwOnFirstException)
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+ Debug.Assert(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode.");
+ Debug.Assert(_nonGreedyState.ReservedSourcesTemp != null, "Should have been initialized");
+
+ List<Exception> exceptions = null;
+
+ List<KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>> reserved = _nonGreedyState.ReservedSourcesTemp;
+ for (int i = 0; i < reserved.Count; i++)
+ {
+ KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>> sourceAndMessage = reserved[i];
+ reserved[i] = default(KeyValuePair<ISourceBlock<T>, KeyValuePair<DataflowMessageHeader, T>>);
+ ISourceBlock<T> source = sourceAndMessage.Key;
+ KeyValuePair<DataflowMessageHeader, T> message = sourceAndMessage.Value;
+ if (source != null && message.Key.IsValid)
+ {
+ try { source.ReleaseReservation(message.Key, _owningBatch); }
+ catch (Exception e)
+ {
+ if (throwOnFirstException) throw;
+ if (exceptions == null) exceptions = new List<Exception>(1);
+ exceptions.Add(e);
+ }
+ }
+ }
+
+ if (exceptions != null) throw new AggregateException(exceptions);
+ }
+
+ /// <summary>Notifies the block that one or more items was removed from the queue.</summary>
+ /// <param name="numItemsRemoved">The number of items removed.</param>
+ internal void OnItemsRemoved(int numItemsRemoved)
+ {
+ Contract.Requires(numItemsRemoved > 0, "Should only be called for a positive number of items removed.");
+ 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;
+
+ ProcessAsyncIfNecessary();
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <summary>Counts the input items in a single output item or in a list of output items.</summary>
+ /// <param name="singleOutputItem">A single output item. Only considered if multipleOutputItems == null.</param>
+ /// <param name="multipleOutputItems">A list of output items. May be null.</param>
+ internal static int CountItems(T[] singleOutputItem, IList<T[]> multipleOutputItems)
+ {
+ // If multipleOutputItems == null, then singleOutputItem is the subject of counting
+ if (multipleOutputItems == null) return singleOutputItem.Length;
+
+ // multipleOutputItems != null. Count the elements in each item.
+ int count = 0;
+ foreach (T[] item in multipleOutputItems) count += item.Length;
+ return count;
+ }
+
+ /// <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 _messages.Count; } }
+
+ /// <summary>Gets information about this helper to be used for display in a debugger.</summary>
+ /// <returns>Debugging information about this target.</returns>
+ internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); }
+
+ /// <summary>Gets the object to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayBatch = _owningBatch as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displayBatch != null ? displayBatch.Content : _owningBatch);
+ }
+ }
+
+ /// <summary>Provides a wrapper for commonly needed debugging information.</summary>
+ internal sealed class DebuggingInformation
+ {
+ /// <summary>The target being viewed.</summary>
+ private BatchBlockTargetCore _target;
+
+ /// <summary>Initializes the debugging helper.</summary>
+ /// <param name="target">The target being viewed.</param>
+ public DebuggingInformation(BatchBlockTargetCore target) { _target = target; }
+
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ public IEnumerable<T> InputQueue { get { return _target._messages.ToList(); } }
+ /// <summary>Gets the task being used for input processing.</summary>
+ public Task TaskForInputProcessing { get { return _target._nonGreedyState != null ? _target._nonGreedyState.TaskForInputProcessing : null; } }
+ /// <summary>Gets the collection of postponed messages.</summary>
+ public QueuedMap<ISourceBlock<T>, DataflowMessageHeader> PostponedMessages { get { return _target._nonGreedyState != null ? _target._nonGreedyState.PostponedMessages : null; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ public bool IsDecliningPermanently { get { return _target._decliningPermanently; } }
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ public GroupingDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } }
+ /// <summary>Gets the number of batches that have been completed.</summary>
+ public long NumberOfBatchesCompleted { get { return _target._batchesCompleted; } }
+ }
+ }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Provides a dataflow block that batches a specified number of inputs of potentially differing types
+ /// provided to one or more of its targets.
+ /// </summary>
+ /// <typeparam name="T1">Specifies the type of data accepted by the block's first target.</typeparam>
+ /// <typeparam name="T2">Specifies the type of data accepted by the block's second target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(BatchedJoinBlock<,>.DebugView))]
+ public sealed class BatchedJoinBlock<T1, T2> : IReceivableSourceBlock<Tuple<IList<T1>, IList<T2>>>, IDebuggerDisplay
+ {
+ /// <summary>The size of the batches generated by this BatchedJoin.</summary>
+ private readonly int _batchSize;
+ /// <summary>State shared among the targets.</summary>
+ private readonly BatchedJoinBlockTargetSharedResources _sharedResources;
+ /// <summary>The target providing inputs of type T1.</summary>
+ private readonly BatchedJoinBlockTarget<T1> _target1;
+ /// <summary>The target providing inputs of type T2.</summary>
+ private readonly BatchedJoinBlockTarget<T2> _target2;
+ /// <summary>The source side.</summary>
+ private readonly SourceCore<Tuple<IList<T1>, IList<T2>>> _source;
+
+ /// <summary>Initializes this <see cref="BatchedJoinBlock{T1,T2}"/> with the specified configuration.</summary>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ public BatchedJoinBlock(Int32 batchSize) :
+ this(batchSize, GroupingDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes this <see cref="BatchedJoinBlock{T1,T2}"/> with the specified configuration.</summary>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BatchedJoinBlock{T1,T2}"/>.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public BatchedJoinBlock(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<Tuple<IList<T1>, IList<T2>>>(
+ this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock<T1, T2>)owningSource).CompleteEachTarget());
+
+ // The action to run when a batch should be created. This is typically called
+ // when we have a full batch, but it will also be called when we're done receiving
+ // messages, and thus when there may be a few stragglers we need to make a batch out of.
+ Action createBatchAction = () =>
+ {
+ if (_target1.Count > 0 || _target2.Count > 0)
+ {
+ _source.AddMessage(Tuple.Create(_target1.GetAndEmptyMessages(), _target2.GetAndEmptyMessages()));
+ }
+ };
+
+ // Configure the targets
+ _sharedResources = new BatchedJoinBlockTargetSharedResources(
+ batchSize, dataflowBlockOptions,
+ createBatchAction,
+ () =>
+ {
+ createBatchAction();
+ _source.Complete();
+ },
+ _source.AddException,
+ Complete);
+ _target1 = new BatchedJoinBlockTarget<T1>(_sharedResources);
+ _target2 = new BatchedJoinBlockTarget<T2>(_sharedResources);
+
+ // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception.
+ // In those cases we need to fault the target half to drop its buffered messages and to release its
+ // reservations. This should not create an infinite loop, because all our implementations are designed
+ // to handle multiple completion requests and to carry over only one.
+ _source.Completion.ContinueWith((completed, state) =>
+ {
+ var thisBlock = ((BatchedJoinBlock<T1, T2>)state) as IDataflowBlock;
+ Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion.");
+ thisBlock.Fault(completed.Exception);
+ }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
+
+ // Handle async cancellation requests by declining on the target
+ Common.WireCancellationToComplete(
+ dataflowBlockOptions.CancellationToken, _source.Completion, state => ((BatchedJoinBlock<T1, T2>)state).CompleteEachTarget(), this);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <summary>Gets the size of the batches generated by this <see cref="BatchedJoinBlock{T1,T2}"/>.</summary>
+ public Int32 BatchSize { get { return _batchSize; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the first type.</summary>
+ public ITargetBlock<T1> Target1 { get { return _target1; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the second type.</summary>
+ public ITargetBlock<T2> Target2 { get { return _target2; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public IDisposable LinkTo(ITargetBlock<Tuple<IList<T1>, IList<T2>>> target, DataflowLinkOptions linkOptions)
+ {
+ return _source.LinkTo(target, linkOptions);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public Boolean TryReceive(Predicate<Tuple<IList<T1>, IList<T2>>> filter, out Tuple<IList<T1>, IList<T2>> item)
+ {
+ return _source.TryReceive(filter, out item);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public bool TryReceiveAll(out IList<Tuple<IList<T1>, IList<T2>>> items) { return _source.TryReceiveAll(out items); }
+
+ /// <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/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { return _source.Completion; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ public void Complete()
+ {
+ Debug.Assert(_target1 != null, "_target1 not initialized");
+ Debug.Assert(_target2 != null, "_target2 not initialized");
+
+ _target1.Complete();
+ _target2.Complete();
+ }
+
+ /// <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();
+
+ 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();
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ Tuple<IList<T1>, IList<T2>> ISourceBlock<Tuple<IList<T1>, IList<T2>>>.ConsumeMessage(
+ DataflowMessageHeader messageHeader, ITargetBlock<Tuple<IList<T1>, IList<T2>>> target, out Boolean messageConsumed)
+ {
+ return _source.ConsumeMessage(messageHeader, target, out messageConsumed);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ bool ISourceBlock<Tuple<IList<T1>, IList<T2>>>.ReserveMessage(
+ DataflowMessageHeader messageHeader, ITargetBlock<Tuple<IList<T1>, IList<T2>>> target)
+ {
+ return _source.ReserveMessage(messageHeader, target);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<Tuple<IList<T1>, IList<T2>>>.ReleaseReservation(
+ DataflowMessageHeader messageHeader, ITargetBlock<Tuple<IList<T1>, IList<T2>>> target)
+ {
+ _source.ReleaseReservation(messageHeader, target);
+ }
+
+ /// <summary>
+ /// Invokes Complete on each target
+ /// </summary>
+ private void CompleteEachTarget()
+ {
+ _target1.Complete();
+ _target2.Complete();
+ }
+
+ /// <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}, BatchSize={1}, OutputCount={2}",
+ Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
+ BatchSize,
+ 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 Transform.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The block being viewed.</summary>
+ private readonly BatchedJoinBlock<T1, T2> _batchedJoinBlock;
+ /// <summary>The source half of the block being viewed.</summary>
+ private readonly SourceCore<Tuple<IList<T1>, IList<T2>>>.DebuggingInformation _sourceDebuggingInformation;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="batchedJoinBlock">The batched join being viewed.</param>
+ public DebugView(BatchedJoinBlock<T1, T2> batchedJoinBlock)
+ {
+ Contract.Requires(batchedJoinBlock != null, "Need a block with which to construct the debug view.");
+ _batchedJoinBlock = batchedJoinBlock;
+ _sourceDebuggingInformation = batchedJoinBlock._source.GetDebuggingInformation();
+ }
+
+ /// <summary>Gets the messages waiting to be received.</summary>
+ public IEnumerable<Tuple<IList<T1>, IList<T2>>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } }
+ /// <summary>Gets the number of batches created.</summary>
+ public long BatchesCreated { get { return _batchedJoinBlock._sharedResources._batchesCreated; } }
+ /// <summary>Gets the number of items remaining to form a batch.</summary>
+ public int RemainingItemsForBatch { get { return _batchedJoinBlock._sharedResources._remainingItemsInBatch; } }
+
+ /// <summary>Gets the size of the batches generated by this BatchedJoin.</summary>
+ public Int32 BatchSize { get { return _batchedJoinBlock._batchSize; } }
+ /// <summary>Gets the first target.</summary>
+ public ITargetBlock<T1> Target1 { get { return _batchedJoinBlock._target1; } }
+ /// <summary>Gets the second target.</summary>
+ public ITargetBlock<T2> Target2 { get { return _batchedJoinBlock._target2; } }
+
+ /// <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 GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } }
+ /// <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(_batchedJoinBlock); } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<Tuple<IList<T1>, IList<T2>>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
+ /// <summary>Gets the target that holds a reservation on the next message, if any.</summary>
+ public ITargetBlock<Tuple<IList<T1>, IList<T2>>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
+ }
+ }
+
+ /// <summary>
+ /// Provides a dataflow block that batches a specified number of inputs of potentially differing types
+ /// provided to one or more of its targets.
+ /// </summary>
+ /// <typeparam name="T1">Specifies the type of data accepted by the block's first target.</typeparam>
+ /// <typeparam name="T2">Specifies the type of data accepted by the block's second target.</typeparam>
+ /// <typeparam name="T3">Specifies the type of data accepted by the block's third target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(BatchedJoinBlock<,,>.DebugView))]
+ [SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public sealed class BatchedJoinBlock<T1, T2, T3> : IReceivableSourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>, IDebuggerDisplay
+ {
+ /// <summary>The size of the batches generated by this BatchedJoin.</summary>
+ private readonly int _batchSize;
+ /// <summary>State shared among the targets.</summary>
+ private readonly BatchedJoinBlockTargetSharedResources _sharedResources;
+ /// <summary>The target providing inputs of type T1.</summary>
+ private readonly BatchedJoinBlockTarget<T1> _target1;
+ /// <summary>The target providing inputs of type T2.</summary>
+ private readonly BatchedJoinBlockTarget<T2> _target2;
+ /// <summary>The target providing inputs of type T3.</summary>
+ private readonly BatchedJoinBlockTarget<T3> _target3;
+ /// <summary>The source side.</summary>
+ private readonly SourceCore<Tuple<IList<T1>, IList<T2>, IList<T3>>> _source;
+
+ /// <summary>Initializes this <see cref="BatchedJoinBlock{T1,T2,T3}"/> with the specified configuration.</summary>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ public BatchedJoinBlock(Int32 batchSize) :
+ this(batchSize, GroupingDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes this <see cref="BatchedJoinBlock{T1,T2,T3}"/> with the specified configuration.</summary>
+ /// <param name="batchSize">The number of items to group into a batch.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BatchedJoinBlock{T1,T2}"/>.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="batchSize"/> must be positive.</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public BatchedJoinBlock(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<Tuple<IList<T1>, IList<T2>, IList<T3>>>(
+ this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock<T1, T2, T3>)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<T1>(_sharedResources);
+ _target2 = new BatchedJoinBlockTarget<T2>(_sharedResources);
+ _target3 = new BatchedJoinBlockTarget<T3>(_sharedResources);
+
+ // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception.
+ // In those cases we need to fault the target half to drop its buffered messages and to release its
+ // reservations. This should not create an infinite loop, because all our implementations are designed
+ // to handle multiple completion requests and to carry over only one.
+ _source.Completion.ContinueWith((completed, state) =>
+ {
+ var thisBlock = ((BatchedJoinBlock<T1, T2, T3>)state) as IDataflowBlock;
+ Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion.");
+ thisBlock.Fault(completed.Exception);
+ }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
+
+ // Handle async cancellation requests by declining on the target
+ Common.WireCancellationToComplete(
+ dataflowBlockOptions.CancellationToken, _source.Completion, state => ((BatchedJoinBlock<T1, T2, T3>)state).CompleteEachTarget(), this);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <summary>Gets the size of the batches generated by this <see cref="BatchedJoinBlock{T1,T2,T3}"/>.</summary>
+ public Int32 BatchSize { get { return _batchSize; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the first type.</summary>
+ public ITargetBlock<T1> Target1 { get { return _target1; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the second type.</summary>
+ public ITargetBlock<T2> Target2 { get { return _target2; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the third type.</summary>
+ public ITargetBlock<T3> Target3 { get { return _target3; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ public IDisposable LinkTo(ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target, DataflowLinkOptions linkOptions)
+ {
+ return _source.LinkTo(target, linkOptions);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public Boolean TryReceive(Predicate<Tuple<IList<T1>, IList<T2>, IList<T3>>> filter, out Tuple<IList<T1>, IList<T2>, IList<T3>> item)
+ {
+ return _source.TryReceive(filter, out item);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public bool TryReceiveAll(out IList<Tuple<IList<T1>, IList<T2>, IList<T3>>> items) { return _source.TryReceiveAll(out items); }
+
+ /// <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/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { return _source.Completion; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ 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();
+ }
+
+ /// <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();
+
+ 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();
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ Tuple<IList<T1>, IList<T2>, IList<T3>> ISourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>.ConsumeMessage(
+ DataflowMessageHeader messageHeader, ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target, out Boolean messageConsumed)
+ {
+ return _source.ConsumeMessage(messageHeader, target, out messageConsumed);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ bool ISourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>.ReserveMessage(
+ DataflowMessageHeader messageHeader, ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target)
+ {
+ return _source.ReserveMessage(messageHeader, target);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>.ReleaseReservation(
+ DataflowMessageHeader messageHeader, ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target)
+ {
+ _source.ReleaseReservation(messageHeader, target);
+ }
+
+ /// <summary>
+ /// Invokes Complete on each target
+ /// </summary>
+ private void CompleteEachTarget()
+ {
+ _target1.Complete();
+ _target2.Complete();
+ _target3.Complete();
+ }
+
+ /// <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}, BatchSize={1}, OutputCount={2}",
+ Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
+ BatchSize,
+ 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 Transform.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The block being viewed.</summary>
+ private readonly BatchedJoinBlock<T1, T2, T3> _batchedJoinBlock;
+ /// <summary>The source half of the block being viewed.</summary>
+ private readonly SourceCore<Tuple<IList<T1>, IList<T2>, IList<T3>>>.DebuggingInformation _sourceDebuggingInformation;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="batchedJoinBlock">The batched join being viewed.</param>
+ public DebugView(BatchedJoinBlock<T1, T2, T3> batchedJoinBlock)
+ {
+ Contract.Requires(batchedJoinBlock != null, "Need a block with which to construct the debug view.");
+ _sourceDebuggingInformation = batchedJoinBlock._source.GetDebuggingInformation();
+ _batchedJoinBlock = batchedJoinBlock;
+ }
+
+ /// <summary>Gets the messages waiting to be received.</summary>
+ public IEnumerable<Tuple<IList<T1>, IList<T2>, IList<T3>>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } }
+ /// <summary>Gets the number of batches created.</summary>
+ public long BatchesCreated { get { return _batchedJoinBlock._sharedResources._batchesCreated; } }
+ /// <summary>Gets the number of items remaining to form a batch.</summary>
+ public int RemainingItemsForBatch { get { return _batchedJoinBlock._sharedResources._remainingItemsInBatch; } }
+
+ /// <summary>Gets the size of the batches generated by this BatchedJoin.</summary>
+ public Int32 BatchSize { get { return _batchedJoinBlock._batchSize; } }
+ /// <summary>Gets the first target.</summary>
+ public ITargetBlock<T1> Target1 { get { return _batchedJoinBlock._target1; } }
+ /// <summary>Gets the second target.</summary>
+ public ITargetBlock<T2> Target2 { get { return _batchedJoinBlock._target2; } }
+ /// <summary>Gets the second target.</summary>
+ public ITargetBlock<T3> Target3 { get { return _batchedJoinBlock._target3; } }
+
+ /// <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 GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } }
+ /// <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(_batchedJoinBlock); } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<Tuple<IList<T1>, IList<T2>, IList<T3>>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
+ /// <summary>Gets the target that holds a reservation on the next message, if any.</summary>
+ public ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
+ }
+ }
+}
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Provides the target used in a BatchedJoin.</summary>
+ /// <typeparam name="T">Specifies the type of data accepted by this target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(BatchedJoinBlockTarget<>.DebugView))]
+ internal sealed class BatchedJoinBlockTarget<T> : ITargetBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>The shared resources used by all targets associated with the same batched join instance.</summary>
+ private readonly BatchedJoinBlockTargetSharedResources _sharedResources;
+ /// <summary>Whether this target is declining future messages.</summary>
+ private bool _decliningPermanently;
+ /// <summary>Input messages for the next batch.</summary>
+ private IList<T> _messages = new List<T>();
+
+ /// <summary>Initializes the target.</summary>
+ /// <param name="sharedResources">The shared resources used by all targets associated with this batched join.</param>
+ 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++;
+ }
+
+ /// <summary>Gets the number of messages buffered in this target.</summary>
+ internal int Count { get { return _messages.Count; } }
+
+ /// <summary>Gets the messages buffered by this target and then empties the collection.</summary>
+ /// <returns>The messages from the target.</returns>
+ internal IList<T> GetAndEmptyMessages()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources._incomingLock, held: true);
+
+ IList<T> toReturn = _messages;
+ _messages = new List<T>();
+ return toReturn;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ public DataflowMessageStatus 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 (_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;
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ 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();
+ }
+ }
+ }
+
+ /// <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();
+
+ lock (_sharedResources._incomingLock)
+ {
+ if (!_decliningPermanently && !_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception);
+ }
+
+ _sharedResources._completionAction();
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } }
+
+ /// <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),
+ _messages.Count);
+ }
+ }
+ /// <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 Transform.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The batched join block target being viewed.</summary>
+ private readonly BatchedJoinBlockTarget<T> _batchedJoinBlockTarget;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="batchedJoinBlockTarget">The batched join target being viewed.</param>
+ public DebugView(BatchedJoinBlockTarget<T> batchedJoinBlockTarget)
+ {
+ Contract.Requires(batchedJoinBlockTarget != null, "Need a block with which to construct the debug view.");
+ _batchedJoinBlockTarget = batchedJoinBlockTarget;
+ }
+
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ public IEnumerable<T> InputQueue { get { return _batchedJoinBlockTarget._messages; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ public bool IsDecliningPermanently
+ {
+ get
+ {
+ return _batchedJoinBlockTarget._decliningPermanently ||
+ _batchedJoinBlockTarget._sharedResources._decliningPermanently;
+ }
+ }
+ }
+ }
+
+ /// <summary>Provides a container for resources shared across all targets used by the same BatchedJoinBlock instance.</summary>
+ internal sealed class BatchedJoinBlockTargetSharedResources
+ {
+ /// <summary>Initializes the shared resources.</summary>
+ /// <param name="batchSize">The size of a batch to create.</param>
+ /// <param name="dataflowBlockOptions">The options used to configure the shared resources. Assumed to be immutable.</param>
+ /// <param name="batchSizeReachedAction">The action to invoke when a batch is completed.</param>
+ /// <param name="allTargetsDecliningAction">The action to invoke when no more targets are accepting input.</param>
+ /// <param name="exceptionAction">The action to invoke when an exception needs to be logged.</param>
+ /// <param name="completionAction">The action to invoke when completing, typically invoked due to a call to Fault.</param>
+ internal BatchedJoinBlockTargetSharedResources(
+ int batchSize, GroupingDataflowBlockOptions dataflowBlockOptions,
+ Action batchSizeReachedAction, Action allTargetsDecliningAction,
+ Action<Exception> 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;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ internal readonly object _incomingLock;
+ /// <summary>The size of the batches to generate.</summary>
+ internal readonly int _batchSize;
+
+ /// <summary>The action to invoke when enough elements have been accumulated to make a batch.</summary>
+ internal readonly Action _batchSizeReachedAction;
+ /// <summary>The action to invoke when all targets are declining further messages.</summary>
+ internal readonly Action _allTargetsDecliningPermanentlyAction;
+ /// <summary>The action to invoke when an exception has to be logged.</summary>
+ internal readonly Action<Exception> _exceptionAction;
+ /// <summary>The action to invoke when the owning block has to be completed.</summary>
+ internal readonly Action _completionAction;
+
+ /// <summary>The number of items remaining to form a batch.</summary>
+ internal int _remainingItemsInBatch;
+ /// <summary>The number of targets still alive (i.e. not declining all further messages).</summary>
+ internal int _remainingAliveTargets;
+ /// <summary>Whether all targets should decline all further messages.</summary>
+ internal bool _decliningPermanently;
+ /// <summary>The number of batches created.</summary>
+ internal long _batchesCreated;
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// BroadcastBlock.cs
+//
+//
+// A propagator that broadcasts incoming messages to all targets, overwriting the current
+// message in the process.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Security;
+using System.Threading.Tasks.Dataflow.Internal;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>
+ /// Provides a buffer for storing at most one element at time, overwriting each message with the next as it arrives.
+ /// Messages are broadcast to all linked targets, all of which may consume a clone of the message.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of the data buffered by this dataflow block.</typeparam>
+ /// <remarks>
+ /// <see cref="BroadcastBlock{T}"/> exposes at most one element at a time. However, unlike
+ /// <see cref="WriteOnceBlock{T}"/>, that element will be overwritten as new elements are provided
+ /// to the block. <see cref="BroadcastBlock{T}"/> ensures that the current element is broadcast to any
+ /// linked targets before allowing the element to be overwritten.
+ /// </remarks>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(BroadcastBlock<>.DebugView))]
+ public sealed class BroadcastBlock<T> : IPropagatorBlock<T, T>, IReceivableSourceBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>The source side.</summary>
+ private readonly BroadcastingSourceCore<T> _source;
+ /// <summary>Bounding state for when the block is executing in bounded mode.</summary>
+ private readonly BoundingStateWithPostponedAndTask<T> _boundingState;
+ /// <summary>Whether all future messages should be declined.</summary>
+ private bool _decliningPermanently;
+ /// <summary>A task has reserved the right to run the completion routine.</summary>
+ private bool _completionReserved;
+ /// <summary>Gets the lock used to synchronize incoming requests.</summary>
+ private object IncomingLock { get { return _source; } }
+
+ /// <summary>Initializes the <see cref="BroadcastBlock{T}"/> with the specified cloning function.</summary>
+ /// <param name="cloningFunction">
+ /// The function to use to clone the data when offered to other blocks.
+ /// This may be null to indicate that no cloning need be performed.
+ /// </param>
+ public BroadcastBlock(Func<T, T> cloningFunction) :
+ this(cloningFunction, DataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes the <see cref="BroadcastBlock{T}"/> with the specified cloning function and <see cref="DataflowBlockOptions"/>.</summary>
+ /// <param name="cloningFunction">
+ /// The function to use to clone the data when offered to other blocks.
+ /// This may be null to indicate that no cloning need be performed.
+ /// </param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="BroadcastBlock{T}"/>.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public BroadcastBlock(Func<T, T> cloningFunction, DataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments
+ 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<int> onItemsRemoved = null;
+ if (dataflowBlockOptions.BoundedCapacity > 0)
+ {
+ Debug.Assert(dataflowBlockOptions.BoundedCapacity > 0, "Positive bounding count expected; should have been verified by options ctor");
+ onItemsRemoved = OnItemsRemoved;
+ _boundingState = new BoundingStateWithPostponedAndTask<T>(dataflowBlockOptions.BoundedCapacity);
+ }
+
+ // Initialize the source side
+ _source = new BroadcastingSourceCore<T>(this, cloningFunction, dataflowBlockOptions, 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 = ((BroadcastBlock<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, state => ((BroadcastBlock<T>)state).Complete(), this);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <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);
+ }
+
+ internal 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 && (!_decliningPermanently || 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 if possible
+ _decliningPermanently = 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"]/*' />
+ Boolean IReceivableSourceBlock<T>.TryReceiveAll(out IList<T> 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="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 (_decliningPermanently)
+ {
+ 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 delegate
+ _source.AddMessage(messageValue);
+ if (_boundingState != null) _boundingState.CurrentCount += 1; // track this new item against our bound
+ 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;
+ }
+ }
+
+ /// <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, "Should only be called for a positive number of items removed.");
+ 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 (!_decliningPermanently &&
+ _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 => ((BroadcastBlock<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. Complete 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 exception)
+ {
+ // Prevent the creation of new processing tasks
+ CompleteCore(exception, 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 (_decliningPermanently &&
+ !_completionReserved &&
+ (_boundingState == null || _boundingState.TaskForInputProcessing == null))
+ {
+ _completionReserved = 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 thisBroadcastBlock = (BroadcastBlock<T>)state;
+
+ // Release any postponed messages
+ List<Exception> exceptions = null;
+ if (thisBroadcastBlock._boundingState != null)
+ {
+ // Note: No locks should be held at this point
+ Common.ReleaseAllPostponedMessages(thisBroadcastBlock,
+ thisBroadcastBlock._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.
+ thisBroadcastBlock._source.AddExceptions(exceptions);
+ }
+
+ thisBroadcastBlock._source.Complete();
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ // Otherwise, we can just decline the source directly.
+ else
+ {
+ _source.Complete();
+ }
+ }
+ }
+
+ /// <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>Gets a value to be used for the DebuggerDisplayAttribute. This must not throw even if HasValue is false.</summary>
+ private bool HasValueForDebugger { get { return _source.GetDebuggingInformation().HasValue; } }
+ /// <summary>Gets a value to be used for the DebuggerDisplayAttribute. This must not throw even if HasValue is false.</summary>
+ private T ValueForDebugger { get { return _source.GetDebuggingInformation().Value; } }
+
+ /// <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}, HasValue={1}, Value={2}",
+ Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
+ HasValueForDebugger,
+ ValueForDebugger);
+ }
+ }
+ /// <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 BroadcastBlock.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The BroadcastBlock being debugged.</summary>
+ private readonly BroadcastBlock<T> _broadcastBlock;
+ /// <summary>Debug info about the source side of the broadcast.</summary>
+ private readonly BroadcastingSourceCore<T>.DebuggingInformation _sourceDebuggingInformation;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="broadcastBlock">The BroadcastBlock being debugged.</param>
+ public DebugView(BroadcastBlock<T> broadcastBlock)
+ {
+ Contract.Requires(broadcastBlock != null, "Need a block with which to construct the debug view.");
+ _broadcastBlock = broadcastBlock;
+ _sourceDebuggingInformation = broadcastBlock._source.GetDebuggingInformation();
+ }
+
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ public IEnumerable<T> InputQueue { get { return _sourceDebuggingInformation.InputQueue; } }
+ /// <summary>Gets whether the broadcast has a current value.</summary>
+ public bool HasValue { get { return _broadcastBlock.HasValueForDebugger; } }
+ /// <summary>Gets the broadcast's current value.</summary>
+ public T Value { get { return _broadcastBlock.ValueForDebugger; } }
+
+ /// <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 _broadcastBlock._decliningPermanently; } }
+ /// <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(_broadcastBlock); } }
+
+ /// <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; } }
+ }
+
+ /// <summary>Provides a core implementation for blocks that implement <see cref="ISourceBlock{TOutput}"/>.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data supplied by the <see cref="SourceCore{TOutput}"/>.</typeparam>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ private sealed class BroadcastingSourceCore<TOutput>
+ {
+ /// <summary>A registry used to store all linked targets and information about them.</summary>
+ private readonly TargetRegistry<TOutput> _targetRegistry;
+ /// <summary>All of the output messages queued up to be received by consumers/targets.</summary>
+ private readonly Queue<TOutput> _messages = new Queue<TOutput>();
+ /// <summary>A TaskCompletionSource that represents the completion of this block.</summary>
+ private readonly TaskCompletionSource<VoidResult> _completionTask = new TaskCompletionSource<VoidResult>();
+ /// <summary>
+ /// An action to be invoked on the owner block when an item is removed.
+ /// This may be null if the owner block doesn't need to be notified.
+ /// </summary>
+ private readonly Action<int> _itemsRemovedAction;
+
+ /// <summary>Gets the object to use as the outgoing lock.</summary>
+ private object OutgoingLock { get { return _completionTask; } }
+ /// <summary>Gets the object to use as the value lock.</summary>
+ private object ValueLock { get { return _targetRegistry; } }
+
+ /// <summary>The source utilize this helper.</summary>
+ private readonly BroadcastBlock<TOutput> _owningSource;
+ /// <summary>The options used to configure this block's execution.</summary>
+ private readonly DataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>The cloning function to use.</summary>
+ private readonly Func<TOutput, TOutput> _cloningFunction;
+
+ /// <summary>An indicator whether _currentMessage has a value.</summary>
+ private bool _currentMessageIsValid;
+ /// <summary>The message currently being broadcast.</summary>
+ private TOutput _currentMessage;
+ /// <summary>The target that the next message is reserved for, or null if nothing is reserved.</summary>
+ private ITargetBlock<TOutput> _nextMessageReservedFor;
+ /// <summary>Whether this block should again attempt to offer messages to targets.</summary>
+ private bool _enableOffering;
+ /// <summary>Whether all future messages should be declined.</summary>
+ private bool _decliningPermanently;
+ /// <summary>The task used to process the output and offer it to targets.</summary>
+ private Task _taskForOutputProcessing;
+ /// <summary>Exceptions that may have occurred and gone unhandled during processing.</summary>
+ private List<Exception> _exceptions;
+ /// <summary>Counter for message IDs unique within this source block.</summary>
+ private long _nextMessageId = 1; // We are going to use this value before incrementing.
+ /// <summary>Whether someone has reserved the right to call CompleteBlockOncePossible.</summary>
+ private bool _completionReserved;
+
+ /// <summary>Initializes the source core.</summary>
+ /// <param name="owningSource">The source utilizing this core.</param>
+ /// <param name="cloningFunction">The function to use to clone the data when offered to other blocks. May be null.</param>
+ /// <param name="dataflowBlockOptions">The options to use to configure the block.</param>
+ /// <param name="itemsRemovedAction">Action to invoke when an item is removed.</param>
+ internal BroadcastingSourceCore(
+ BroadcastBlock<TOutput> owningSource,
+ Func<TOutput, TOutput> cloningFunction,
+ DataflowBlockOptions dataflowBlockOptions,
+ Action<int> itemsRemovedAction)
+ {
+ Contract.Requires(owningSource != null, "Must be associated with a broadcast block.");
+ Contract.Requires(dataflowBlockOptions != null, "Options are required to configure this block.");
+
+ // Store the arguments
+ _owningSource = owningSource;
+ _cloningFunction = cloningFunction;
+ _dataflowBlockOptions = dataflowBlockOptions;
+ _itemsRemovedAction = itemsRemovedAction;
+
+ // Construct members that depend on the arguments
+ _targetRegistry = new TargetRegistry<TOutput>(_owningSource);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ internal Boolean TryReceive(Predicate<TOutput> filter, out TOutput item)
+ {
+ // Take the lock only long enough to get the message,
+ // synchronizing with other activities on the block.
+ // We don't want to execute the user-provided cloning delegate
+ // while holding the lock.
+ TOutput message;
+ bool isValid;
+ lock (OutgoingLock)
+ {
+ lock (ValueLock)
+ {
+ message = _currentMessage;
+ isValid = _currentMessageIsValid;
+ }
+ }
+
+ // Clone and hand back a message if we have one and if it passes the filter.
+ // (A null filter means all messages pass.)
+ if (isValid && (filter == null || filter(message)))
+ {
+ item = CloneItem(message);
+ return true;
+ }
+ else
+ {
+ item = default(TOutput);
+ return false;
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ internal Boolean TryReceiveAll(out IList<TOutput> items)
+ {
+ // Try to receive the one item this block may have.
+ // If we can, give back an array of one item. Otherwise, give back null.
+ TOutput item;
+ if (TryReceive(null, out item))
+ {
+ items = new TOutput[] { item };
+ return true;
+ }
+ else
+ {
+ items = null;
+ return false;
+ }
+ }
+
+ /// <summary>Adds a message to the source block for propagation.</summary>
+ /// <param name="item">The item to be wrapped in a message to be added.</param>
+ internal void AddMessage(TOutput item)
+ {
+ // This method must not take the outgoing lock, as it will be called in situations
+ // where a derived type's incoming lock is held. The lock leveling structure
+ // we're employing is such that outgoing may be held while acquiring incoming, but
+ // of course not the other way around. This is the reason why DataflowSourceBlock
+ // needs ValueLock as well. Otherwise, it would be pure overhead.
+ lock (ValueLock)
+ {
+ if (_decliningPermanently) return;
+ _messages.Enqueue(item);
+ if (_messages.Count == 1) _enableOffering = true;
+ OfferAsyncIfNecessary();
+ }
+ }
+
+ /// <summary>Informs the block that it will not be receiving additional messages.</summary>
+ internal void Complete()
+ {
+ lock (ValueLock)
+ {
+ _decliningPermanently = true;
+
+ // Complete may be called in a context where an incoming lock is held. We need to
+ // call CompleteBlockIfPossible, but we can't do so if the incoming lock is held.
+ // However, now that _decliningPermanently has been set, the timing of
+ // CompleteBlockIfPossible doesn't matter, so we schedule it to run asynchronously
+ // and take the necessary locks in a situation where we're sure it won't cause a problem.
+ Task.Factory.StartNew(state =>
+ {
+ var thisSourceCore = (BroadcastingSourceCore<TOutput>)state;
+ lock (thisSourceCore.OutgoingLock)
+ {
+ lock (thisSourceCore.ValueLock)
+ {
+ thisSourceCore.CompleteBlockIfPossible();
+ }
+ }
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>Clones the item.</summary>
+ /// <param name="item">The item to clone.</param>
+ /// <returns>The cloned item.</returns>
+ private TOutput CloneItem(TOutput item)
+ {
+ return _cloningFunction != null ?
+ _cloningFunction(item) :
+ item;
+ }
+
+ /// <summary>Offers the current message to a specific target.</summary>
+ /// <param name="target">The target to which to offer the current message.</param>
+ private void OfferCurrentMessageToNewTarget(ITargetBlock<TOutput> target)
+ {
+ Contract.Requires(target != null, "Target required to offer messages to.");
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ // Get the current message if there is one
+ TOutput currentMessage;
+ bool isValid;
+ lock (ValueLock)
+ {
+ currentMessage = _currentMessage;
+ isValid = _currentMessageIsValid;
+ }
+
+ // If there is no valid message yet, there is nothing to offer
+ if (!isValid) return;
+
+ // Offer it to the target.
+ // We must not increment the message ID here. We only do that when we populate _currentMessage, i.e. when we dequeue.
+ bool useCloning = _cloningFunction != null;
+ DataflowMessageStatus result = target.OfferMessage(new DataflowMessageHeader(_nextMessageId), currentMessage, _owningSource, consumeToAccept: useCloning);
+
+ // If accepted and the target was linked as "unlinkAfterOne", remove it
+ if (result == DataflowMessageStatus.Accepted)
+ {
+ if (!useCloning)
+ {
+ // If accepted and the target was linked as "once", mark it for removal.
+ // If we were forcing consumption, this removal would have already
+ // happened in ConsumeMessage.
+ _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true);
+ }
+ }
+ // If declined permanently, remove it
+ else if (result == DataflowMessageStatus.DecliningPermanently)
+ {
+ _targetRegistry.Remove(target);
+ }
+ else Debug.Assert(result != DataflowMessageStatus.NotAvailable, "Messages from a Broadcast should never be missed.");
+ }
+
+ /// <summary>Offers messages to targets.</summary>
+ private bool OfferToTargets()
+ {
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ DataflowMessageHeader header = default(DataflowMessageHeader);
+ TOutput message = default(TOutput);
+ int numDequeuedMessages = 0;
+ lock (ValueLock)
+ {
+ // If there's a reservation or there aren't any more messages,
+ // there's nothing for us to do. If there's no reservation
+ // and a message is available, dequeue the next one and store it
+ // as the new current. If we're now at 0 message, disable further
+ // propagation until more messages arrive.
+ if (_nextMessageReservedFor == null && _messages.Count > 0)
+ {
+ // If there are no targets registered, we might as well empty out the broadcast,
+ // keeping just the last. Otherwise, it'll happen anyway, but much more expensively.
+ if (_targetRegistry.FirstTargetNode == null)
+ {
+ while (_messages.Count > 1)
+ {
+ _messages.Dequeue();
+ numDequeuedMessages++;
+ }
+ }
+
+ // Get the next message to offer
+ Debug.Assert(_messages.Count > 0, "There must be at least one message to dequeue.");
+ _currentMessage = message = _messages.Dequeue();
+ numDequeuedMessages++;
+ _currentMessageIsValid = true;
+ header = new DataflowMessageHeader(++_nextMessageId);
+ if (_messages.Count == 0) _enableOffering = false;
+ }
+ else
+ {
+ _enableOffering = false;
+ return false;
+ }
+ } // must not hold ValueLock when calling out to targets
+
+ // Offer the message
+ if (header.IsValid)
+ {
+ // Notify the owner block that our count has decreased
+ if (_itemsRemovedAction != null) _itemsRemovedAction(numDequeuedMessages);
+
+ // Offer it to each target, unless a soleTarget was provided, which case just offer it to that one.
+ TargetRegistry<TOutput>.LinkedTargetInfo cur = _targetRegistry.FirstTargetNode;
+ while (cur != null)
+ {
+ // Note that during OfferMessage, a target may call ConsumeMessage, which may unlink the target
+ // if the target is registered as "once". Doing so will remove the target from the targets list.
+ // As such, we avoid using an enumerator over _targetRegistry and instead walk from back to front,
+ // so that if an element is removed, it won't affect the rest of our walk.
+ TargetRegistry<TOutput>.LinkedTargetInfo next = cur.Next;
+ ITargetBlock<TOutput> target = cur.Target;
+ OfferMessageToTarget(header, message, target);
+ cur = next;
+ }
+ }
+ return true;
+ }
+
+ /// <summary>Offers the specified message to the specified target.</summary>
+ /// <param name="header">The header of the message to offer.</param>
+ /// <param name="message">The message to offer.</param>
+ /// <param name="target">The target to which the message should be offered.</param>
+ /// <remarks>
+ /// This will remove the target from the target registry if the result of the propagation demands it.
+ /// </remarks>
+ private void OfferMessageToTarget(DataflowMessageHeader header, TOutput message, ITargetBlock<TOutput> target)
+ {
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ // Offer the message. If there's a cloning function, we force the target to
+ // come back to us to consume the message, allowing us the opportunity to run
+ // the cloning function once we know they want the data. If there is no cloning
+ // function, there's no reason for them to call back here.
+ bool useCloning = _cloningFunction != null;
+ switch (target.OfferMessage(header, message, _owningSource, consumeToAccept: useCloning))
+ {
+ case DataflowMessageStatus.Accepted:
+ if (!useCloning)
+ {
+ // If accepted and the target was linked as "once", mark it for removal.
+ // If we were forcing consumption, this removal would have already
+ // happened in ConsumeMessage.
+ _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true);
+ }
+ break;
+
+ case DataflowMessageStatus.DecliningPermanently:
+ // If declined permanently, mark the target for removal
+ _targetRegistry.Remove(target);
+ break;
+
+ case DataflowMessageStatus.NotAvailable:
+ Debug.Assert(false, "Messages from a Broadcast should never be missed.");
+ break;
+ // No action required for Postponed or Declined
+ }
+ }
+
+ /// <summary>Called when we want to enable asynchronously offering message to targets.</summary>
+ /// <param name="isReplacementReplica">Whether this call is the continuation of a previous message loop.</param>
+ private void OfferAsyncIfNecessary(bool isReplacementReplica = false)
+ {
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+ // This method must not take the OutgoingLock.
+
+ bool currentlyProcessing = _taskForOutputProcessing != null;
+ bool processingToDo = _enableOffering && _messages.Count > 0;
+
+ // If there's any work to be done...
+ if (!currentlyProcessing && processingToDo && !CanceledOrFaulted)
+ {
+ // Create task and store into _taskForOutputProcessing prior to scheduling the task
+ // so that _taskForOutputProcessing will be visibly set in the task loop.
+ _taskForOutputProcessing = new Task(thisSourceCore => ((BroadcastingSourceCore<TOutput>)thisSourceCore).OfferMessagesLoopCore(), this,
+ Common.GetCreationOptionsForTask(isReplacementReplica));
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ _owningSource, _taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _messages.Count);
+ }
+#endif
+
+ // Start the task handling scheduling exceptions
+ Exception exception = Common.StartTaskSafe(_taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler);
+ if (exception != null)
+ {
+ // First, log the exception while the processing state is dirty which is preventing the block from completing.
+ // Then revert the proactive processing state changes.
+ // And last, try to complete the block.
+ AddException(exception);
+ _decliningPermanently = true;
+ _taskForOutputProcessing = null;
+
+ // Get out from under currently held locks - ValueLock is taken, but OutgoingLock may not be.
+ // Re-take the locks on a separate thread.
+ Task.Factory.StartNew(state =>
+ {
+ var thisSourceCore = (BroadcastingSourceCore<TOutput>)state;
+ lock (thisSourceCore.OutgoingLock)
+ {
+ lock (thisSourceCore.ValueLock)
+ {
+ thisSourceCore.CompleteBlockIfPossible();
+ }
+ }
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+ }
+
+ /// <summary>Task body used to process messages.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void OfferMessagesLoopCore()
+ {
+ try
+ {
+ int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask;
+ lock (OutgoingLock)
+ {
+ // Offer as many messages as we can
+ for (int counter = 0;
+ counter < maxMessagesPerTask && !CanceledOrFaulted;
+ counter++)
+ {
+ if (!OfferToTargets()) break;
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ _owningSource.CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: true);
+ }
+ finally
+ {
+ lock (OutgoingLock)
+ {
+ lock (ValueLock)
+ {
+ // We're no longer processing, so null out the processing task
+ _taskForOutputProcessing = 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.
+ OfferAsyncIfNecessary(isReplacementReplica: true);
+
+ // If, however, we stopped because we ran out of work to do and we
+ // know we'll never get more, then complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+ }
+
+ /// <summary>Completes the block's processing if there's nothing left to do and never will be.</summary>
+ private void CompleteBlockIfPossible()
+ {
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+
+ if (!_completionReserved)
+ {
+ bool currentlyProcessing = _taskForOutputProcessing != null;
+ bool noMoreMessages = _decliningPermanently && _messages.Count == 0;
+
+ // Are we done forever?
+ bool complete = !currentlyProcessing && (noMoreMessages || CanceledOrFaulted);
+ if (complete)
+ {
+ CompleteBlockIfPossible_Slow();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Slow path for CompleteBlockIfPossible.
+ /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined.
+ /// </summary>
+ private void CompleteBlockIfPossible_Slow()
+ {
+ Contract.Requires(_taskForOutputProcessing == null, "There must be no processing tasks.");
+ Contract.Requires(
+ (_decliningPermanently && _messages.Count == 0) || CanceledOrFaulted,
+ "There must be no more messages or the block must be canceled or faulted.");
+
+ _completionReserved = true;
+
+ // Run asynchronously to get out of the currently held locks
+ Task.Factory.StartNew(thisSourceCore => ((BroadcastingSourceCore<TOutput>)thisSourceCore).CompleteBlockOncePossible(),
+ this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+
+ /// <summary>
+ /// Completes the block. This must only be called once, and only once all of the completion conditions are met.
+ /// As such, it must only be called from CompleteBlockIfPossible.
+ /// </summary>
+ private void CompleteBlockOncePossible()
+ {
+ TargetRegistry<TOutput>.LinkedTargetInfo linkedTargets;
+ List<Exception> exceptions;
+
+ // Clear out the target registry and buffers to help avoid memory leaks.
+ // We do not clear _currentMessage, which should remain as that message forever.
+ lock (OutgoingLock)
+ {
+ // Save the linked list of targets so that it could be traversed later to propagate completion
+ linkedTargets = _targetRegistry.ClearEntryPoints();
+ lock (ValueLock)
+ {
+ _messages.Clear();
+
+ // Save a local reference to the exceptions list and null out the field,
+ // so that if the target side tries to add an exception this late,
+ // it will go to a separate list (that will be ignored.)
+ exceptions = _exceptions;
+ _exceptions = null;
+ }
+ }
+
+ // If it's due to an exception, finish in a faulted state
+ if (exceptions != null)
+ {
+ _completionTask.TrySetException(exceptions);
+ }
+ // It's due to cancellation, finish in a canceled state
+ else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ {
+ _completionTask.TrySetCanceled();
+ }
+ // Otherwise, finish in a successful state.
+ else
+ {
+ _completionTask.TrySetResult(default(VoidResult));
+ }
+
+ // Now that the completion task is completed, we may propagate completion to the linked targets
+ _targetRegistry.PropagateCompletion(linkedTargets);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCompleted(_owningSource);
+ }
+#endif
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ internal IDisposable LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions)
+ {
+ // Validate arguments
+ if (target == null) throw new ArgumentNullException("target");
+ if (linkOptions == null) throw new ArgumentNullException("linkOptions");
+ Contract.EndContractBlock();
+
+ lock (OutgoingLock)
+ {
+ // If we've completed or completion has at least started, offer the message to this target,
+ // and propagate completion if that was requested.
+ // Then there's nothing more to be done.
+ if (_completionReserved)
+ {
+ OfferCurrentMessageToNewTarget(target);
+ if (linkOptions.PropagateCompletion) Common.PropagateCompletionOnceCompleted(_completionTask.Task, target);
+ return Disposables.Nop;
+ }
+
+ // Otherwise, add the target and then offer it the current
+ // message. We do this in this order because offering may
+ // cause the target to be removed if it's unlinkAfterOne,
+ // and in the reverse order we would end up adding the target
+ // after it was "removed".
+ _targetRegistry.Add(ref target, linkOptions);
+ OfferCurrentMessageToNewTarget(target);
+ return Common.CreateUnlinker(OutgoingLock, _targetRegistry, target);
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ internal TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out Boolean messageConsumed)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ TOutput valueToClone;
+ lock (OutgoingLock) // We may currently be calling out under this lock to the target; requires it to be reentrant
+ {
+ lock (ValueLock)
+ {
+ // If this isn't the next message to be served up, bail
+ if (messageHeader.Id != _nextMessageId)
+ {
+ messageConsumed = false;
+ return default(TOutput);
+ }
+
+ // If the caller has the reservation, release the reservation.
+ // We still allow others to take the message if there's a reservation.
+ if (_nextMessageReservedFor == target)
+ {
+ _nextMessageReservedFor = null;
+ _enableOffering = true;
+ }
+ _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true);
+
+ OfferAsyncIfNecessary();
+ CompleteBlockIfPossible();
+
+ // Return a clone of the consumed message.
+ valueToClone = _currentMessage;
+ }
+ }
+
+ messageConsumed = true;
+ return CloneItem(valueToClone);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ internal Boolean ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ lock (OutgoingLock)
+ {
+ // If no one currently holds a reservation...
+ if (_nextMessageReservedFor == null)
+ {
+ lock (ValueLock)
+ {
+ // ...and the requested message is next in line, allow it
+ if (messageHeader.Id == _nextMessageId)
+ {
+ _nextMessageReservedFor = target;
+ _enableOffering = false;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ internal void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ lock (OutgoingLock)
+ {
+ // If someone else holds the reservation, bail.
+ if (_nextMessageReservedFor != target) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+
+ TOutput messageToReoffer;
+ lock (ValueLock)
+ {
+ // If this is not the message at the head of the queue, bail
+ if (messageHeader.Id != _nextMessageId) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+
+ // Otherwise, release the reservation, and reoffer the message to all targets.
+ _nextMessageReservedFor = null;
+ _enableOffering = true;
+ messageToReoffer = _currentMessage;
+ OfferAsyncIfNecessary();
+ }
+
+ // We need to explicitly reoffer this message to the releaser,
+ // as otherwise if the target has join behavior it could end up waiting for an offer from
+ // this broadcast forever, even though data is in fact available. We could only
+ // do this if _messages.Count == 0, as if it's > 0 the message will get overwritten
+ // as part of the asynchronous offering, but for consistency we should always reoffer
+ // the current message.
+ OfferMessageToTarget(messageHeader, messageToReoffer, target);
+ }
+ }
+
+ /// <summary>Gets whether the source has had cancellation requested or an exception has occurred.</summary>
+ private bool CanceledOrFaulted
+ {
+ get
+ {
+ // Cancellation is honored as soon as the CancellationToken has been signaled.
+ // Faulting is honored after an exception has been encountered and the owning block
+ // has invoked Complete on us.
+ return _dataflowBlockOptions.CancellationToken.IsCancellationRequested ||
+ (Volatile.Read(ref _exceptions) != null && _decliningPermanently);
+ }
+ }
+
+ /// <summary>Adds an individual exceptionto this source.</summary>
+ /// <param name="exception">The exception to add</param>
+ internal void AddException(Exception exception)
+ {
+ Contract.Requires(exception != null, "An exception to add is required.");
+ Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions.");
+ lock (ValueLock)
+ {
+ Common.AddException(ref _exceptions, exception);
+ }
+ }
+
+ /// <summary>Adds exceptions to this source.</summary>
+ /// <param name="exceptions">The exceptions to add</param>
+ internal void AddExceptions(List<Exception> exceptions)
+ {
+ Contract.Requires(exceptions != null, "A list of exceptions to add is required.");
+ Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions.");
+ lock (ValueLock)
+ {
+ foreach (Exception exception in exceptions)
+ {
+ Common.AddException(ref _exceptions, exception);
+ }
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ internal Task Completion { get { return _completionTask.Task; } }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal DataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } }
+
+ /// <summary>Gets the object to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displaySource = _owningSource as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displaySource != null ? displaySource.Content : _owningSource);
+ }
+ }
+
+ /// <summary>Gets information about this helper to be used for display in a debugger.</summary>
+ /// <returns>Debugging information about this source core.</returns>
+ internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); }
+
+ /// <summary>Provides debugging information about the source core.</summary>
+ internal sealed class DebuggingInformation
+ {
+ /// <summary>The source being viewed.</summary>
+ private BroadcastingSourceCore<TOutput> _source;
+
+ /// <summary>Initializes the type proxy.</summary>
+ /// <param name="source">The source being viewed.</param>
+ public DebuggingInformation(BroadcastingSourceCore<TOutput> source) { _source = source; }
+
+ /// <summary>Gets whether the source contains a current message.</summary>
+ public bool HasValue { get { return _source._currentMessageIsValid; } }
+ /// <summary>Gets the value of the source's current message.</summary>
+ public TOutput Value { get { return _source._currentMessage; } }
+ /// <summary>Gets the number of messages waiting to be made current.</summary>
+ public int InputCount { get { return _source._messages.Count; } }
+ /// <summary>Gets the messages available for receiving.</summary>
+ public IEnumerable<TOutput> InputQueue { get { return _source._messages.ToList(); } }
+ /// <summary>Gets the task being used for output processing.</summary>
+ public Task TaskForOutputProcessing { get { return _source._taskForOutputProcessing; } }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ public DataflowBlockOptions DataflowBlockOptions { get { return _source._dataflowBlockOptions; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ public bool IsDecliningPermanently { get { return _source._decliningPermanently; } }
+ /// <summary>Gets whether the block is completed.</summary>
+ public bool IsCompleted { get { return _source.Completion.IsCompleted; } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<TOutput> LinkedTargets { get { return _source._targetRegistry; } }
+ /// <summary>Gets the target that holds a reservation on the next message, if any.</summary>
+ public ITargetBlock<TOutput> NextMessageReservedFor { get { return _source._nextMessageReservedFor; } }
+ }
+ }
+ }
+}
--- /dev/null
+// 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; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// JoinBlock.cs
+//
+//
+// Blocks that join multiple messages of different types together into a tuple,
+// with one item per type.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Security;
+using System.Threading.Tasks.Dataflow.Internal;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>
+ /// Provides a dataflow block that joins across multiple dataflow sources, not necessarily of the same type,
+ /// waiting for one item to arrive for each type before they’re all released together as a tuple of one item per type.
+ /// </summary>
+ /// <typeparam name="T1">Specifies the type of data accepted by the block's first target.</typeparam>
+ /// <typeparam name="T2">Specifies the type of data accepted by the block's second target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(JoinBlock<,>.DebugView))]
+ public sealed class JoinBlock<T1, T2> : IReceivableSourceBlock<Tuple<T1, T2>>, IDebuggerDisplay
+ {
+ /// <summary>Resources shared by all targets for this join block.</summary>
+ private readonly JoinBlockTargetSharedResources _sharedResources;
+ /// <summary>The source half of this join.</summary>
+ private readonly SourceCore<Tuple<T1, T2>> _source;
+ /// <summary>The first target.</summary>
+ private readonly JoinBlockTarget<T1> _target1;
+ /// <summary>The second target.</summary>
+ private readonly JoinBlockTarget<T2> _target2;
+
+ /// <summary>Initializes the <see cref="JoinBlock{T1,T2}"/>.</summary>
+ public JoinBlock() :
+ this(GroupingDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes the <see cref="JoinBlock{T1,T2}"/>.</summary>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="JoinBlock{T1,T2}"/>.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public JoinBlock(GroupingDataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments
+ 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<Tuple<T1, T2>>, int> onItemsRemoved = null;
+ if (dataflowBlockOptions.BoundedCapacity > 0) onItemsRemoved = (owningSource, count) => ((JoinBlock<T1, T2>)owningSource)._sharedResources.OnItemsRemoved(count);
+
+ // Configure the source
+ _source = new SourceCore<Tuple<T1, T2>>(this, dataflowBlockOptions,
+ owningSource => ((JoinBlock<T1, T2>)owningSource)._sharedResources.CompleteEachTarget(),
+ onItemsRemoved);
+
+ // Configure targets
+ var targets = new JoinBlockTargetBase[2];
+ _sharedResources = new JoinBlockTargetSharedResources(this, targets,
+ () =>
+ {
+ _source.AddMessage(Tuple.Create(_target1.GetOneMessage(), _target2.GetOneMessage()));
+ },
+ exception =>
+ {
+ Volatile.Write(ref _sharedResources._hasExceptions, true);
+ _source.AddException(exception);
+ },
+ dataflowBlockOptions);
+ targets[0] = _target1 = new JoinBlockTarget<T1>(_sharedResources);
+ targets[1] = _target2 = new JoinBlockTarget<T2>(_sharedResources);
+
+ // Let the source know when all targets have completed
+ Task.Factory.ContinueWhenAll(
+ new[] { _target1.CompletionTaskInternal, _target2.CompletionTaskInternal },
+ _ => _source.Complete(),
+ 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 = ((JoinBlock<T1, T2>)state) as IDataflowBlock;
+ Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion.");
+ thisBlock.Fault(completed.Exception);
+ }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
+
+ // Handle async cancellation requests by declining on the target
+ Common.WireCancellationToComplete(
+ dataflowBlockOptions.CancellationToken, _source.Completion, state => ((JoinBlock<T1, T2>)state)._sharedResources.CompleteEachTarget(), this);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ public IDisposable LinkTo(ITargetBlock<Tuple<T1, T2>> target, DataflowLinkOptions linkOptions)
+ {
+ return _source.LinkTo(target, linkOptions);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ public Boolean TryReceive(Predicate<Tuple<T1, T2>> filter, out Tuple<T1, T2> item)
+ {
+ return _source.TryReceive(filter, out item);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public bool TryReceiveAll(out IList<Tuple<T1, T2>> items) { return _source.TryReceiveAll(out items); }
+
+ /// <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/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { return _source.Completion; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ public void Complete()
+ {
+ Debug.Assert(_target1 != null, "_target1 not initialized");
+ Debug.Assert(_target2 != null, "_target2 not initialized");
+
+ _target1.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false);
+ _target2.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: 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();
+
+ Debug.Assert(_sharedResources != null, "_sharedResources not initialized");
+ Debug.Assert(_sharedResources._exceptionAction != null, "_sharedResources._exceptionAction not initialized");
+
+ lock (_sharedResources.IncomingLock)
+ {
+ if (!_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception);
+ }
+
+ Complete();
+ }
+
+ /// <summary>Gets a target that may be used to offer messages of the first type.</summary>
+ public ITargetBlock<T1> Target1 { get { return _target1; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the second type.</summary>
+ public ITargetBlock<T2> Target2 { get { return _target2; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ Tuple<T1, T2> ISourceBlock<Tuple<T1, T2>>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2>> target, out Boolean messageConsumed)
+ {
+ return _source.ConsumeMessage(messageHeader, target, out messageConsumed);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ bool ISourceBlock<Tuple<T1, T2>>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2>> target)
+ {
+ return _source.ReserveMessage(messageHeader, target);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<Tuple<T1, T2>>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2>> 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 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}, OutputCount={1}",
+ Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
+ 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 JoinBlock.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The JoinBlock being viewed.</summary>
+ private readonly JoinBlock<T1, T2> _joinBlock;
+ /// <summary>The source half of the block being viewed.</summary>
+ private readonly SourceCore<Tuple<T1, T2>>.DebuggingInformation _sourceDebuggingInformation;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="joinBlock">The JoinBlock being viewed.</param>
+ public DebugView(JoinBlock<T1, T2> joinBlock)
+ {
+ Contract.Requires(joinBlock != null, "Need a block with which to construct the debug view.");
+ _joinBlock = joinBlock;
+ _sourceDebuggingInformation = joinBlock._source.GetDebuggingInformation();
+ }
+
+ /// <summary>Gets the messages waiting to be received.</summary>
+ public IEnumerable<Tuple<T1, T2>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } }
+ /// <summary>Gets the number of joins created thus far.</summary>
+ public long JoinsCreated { get { return _joinBlock._sharedResources._joinsCreated; } }
+
+ /// <summary>Gets the task being used for input processing.</summary>
+ public Task TaskForInputProcessing { get { return _joinBlock._sharedResources._taskForInputProcessing; } }
+ /// <summary>Gets the task being used for output processing.</summary>
+ public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } }
+
+ /// <summary>Gets the GroupingDataflowBlockOptions used to configure this block.</summary>
+ public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ public bool IsDecliningPermanently { get { return _joinBlock._sharedResources._decliningPermanently; } }
+ /// <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(_joinBlock); } }
+
+ /// <summary>Gets the first target.</summary>
+ public ITargetBlock<T1> Target1 { get { return _joinBlock._target1; } }
+ /// <summary>Gets the second target.</summary>
+ public ITargetBlock<T2> Target2 { get { return _joinBlock._target2; } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<Tuple<T1, T2>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public ITargetBlock<Tuple<T1, T2>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
+ }
+ }
+
+ /// <summary>
+ /// Provides a dataflow block that joins across multiple dataflow sources, not necessarily of the same type,
+ /// waiting for one item to arrive for each type before they’re all released together as a tuple of one item per type.
+ /// </summary>
+ /// <typeparam name="T1">Specifies the type of data accepted by the block's first target.</typeparam>
+ /// <typeparam name="T2">Specifies the type of data accepted by the block's second target.</typeparam>
+ /// <typeparam name="T3">Specifies the type of data accepted by the block's third target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(JoinBlock<,,>.DebugView))]
+ [SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public sealed class JoinBlock<T1, T2, T3> : IReceivableSourceBlock<Tuple<T1, T2, T3>>, IDebuggerDisplay
+ {
+ /// <summary>Resources shared by all targets for this join block.</summary>
+ private readonly JoinBlockTargetSharedResources _sharedResources;
+ /// <summary>The source half of this join.</summary>
+ private readonly SourceCore<Tuple<T1, T2, T3>> _source;
+ /// <summary>The first target.</summary>
+ private readonly JoinBlockTarget<T1> _target1;
+ /// <summary>The second target.</summary>
+ private readonly JoinBlockTarget<T2> _target2;
+ /// <summary>The third target.</summary>
+ private readonly JoinBlockTarget<T3> _target3;
+
+ /// <summary>Initializes the <see cref="JoinBlock{T1,T2,T3}"/>.</summary>
+ public JoinBlock() :
+ this(GroupingDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes the <see cref="JoinBlock{T1,T2,T3}"/>.</summary>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="JoinBlock{T1,T2}"/>.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public JoinBlock(GroupingDataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments
+ 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<Tuple<T1, T2, T3>>, int> onItemsRemoved = null;
+ if (dataflowBlockOptions.BoundedCapacity > 0) onItemsRemoved = (owningSource, count) => ((JoinBlock<T1, T2, T3>)owningSource)._sharedResources.OnItemsRemoved(count);
+
+ // Configure the source
+ _source = new SourceCore<Tuple<T1, T2, T3>>(this, dataflowBlockOptions,
+ owningSource => ((JoinBlock<T1, T2, T3>)owningSource)._sharedResources.CompleteEachTarget(),
+ onItemsRemoved);
+
+ // Configure the targets
+ var targets = new JoinBlockTargetBase[3];
+ _sharedResources = new JoinBlockTargetSharedResources(this, targets,
+ () => _source.AddMessage(Tuple.Create(_target1.GetOneMessage(), _target2.GetOneMessage(), _target3.GetOneMessage())),
+ exception =>
+ {
+ Volatile.Write(ref _sharedResources._hasExceptions, true);
+ _source.AddException(exception);
+ },
+ dataflowBlockOptions);
+ targets[0] = _target1 = new JoinBlockTarget<T1>(_sharedResources);
+ targets[1] = _target2 = new JoinBlockTarget<T2>(_sharedResources);
+ targets[2] = _target3 = new JoinBlockTarget<T3>(_sharedResources);
+
+ // Let the source know when all targets have completed
+ Task.Factory.ContinueWhenAll(
+ new[] { _target1.CompletionTaskInternal, _target2.CompletionTaskInternal, _target3.CompletionTaskInternal },
+ _ => _source.Complete(),
+ 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 = ((JoinBlock<T1, T2, T3>)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 => ((JoinBlock<T1, T2, T3>)state)._sharedResources.CompleteEachTarget(), this);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ public IDisposable LinkTo(ITargetBlock<Tuple<T1, T2, T3>> target, DataflowLinkOptions linkOptions)
+ {
+ return _source.LinkTo(target, linkOptions);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ public Boolean TryReceive(Predicate<Tuple<T1, T2, T3>> filter, out Tuple<T1, T2, T3> item)
+ {
+ return _source.TryReceive(filter, out item);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public bool TryReceiveAll(out IList<Tuple<T1, T2, T3>> items) { return _source.TryReceiveAll(out items); }
+
+ /// <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/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { return _source.Completion; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ 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.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false);
+ _target2.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false);
+ _target3.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: 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();
+
+ Debug.Assert(_sharedResources != null, "_sharedResources not initialized");
+ Debug.Assert(_sharedResources._exceptionAction != null, "_sharedResources._exceptionAction not initialized");
+
+ lock (_sharedResources.IncomingLock)
+ {
+ if (!_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception);
+ }
+
+ Complete();
+ }
+
+ /// <summary>Gets a target that may be used to offer messages of the first type.</summary>
+ public ITargetBlock<T1> Target1 { get { return _target1; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the second type.</summary>
+ public ITargetBlock<T2> Target2 { get { return _target2; } }
+
+ /// <summary>Gets a target that may be used to offer messages of the third type.</summary>
+ public ITargetBlock<T3> Target3 { get { return _target3; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ Tuple<T1, T2, T3> ISourceBlock<Tuple<T1, T2, T3>>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2, T3>> target, out Boolean messageConsumed)
+ {
+ return _source.ConsumeMessage(messageHeader, target, out messageConsumed);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ bool ISourceBlock<Tuple<T1, T2, T3>>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2, T3>> target)
+ {
+ return _source.ReserveMessage(messageHeader, target);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<Tuple<T1, T2, T3>>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2, T3>> 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 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} OutputCount={1}",
+ Common.GetNameForDebugger(this, _source.DataflowBlockOptions),
+ 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 Batch.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The JoinBlock being viewed.</summary>
+ private readonly JoinBlock<T1, T2, T3> _joinBlock;
+ /// <summary>The source half of the block being viewed.</summary>
+ private readonly SourceCore<Tuple<T1, T2, T3>>.DebuggingInformation _sourceDebuggingInformation;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="joinBlock">The JoinBlock being viewed.</param>
+ public DebugView(JoinBlock<T1, T2, T3> joinBlock)
+ {
+ Contract.Requires(joinBlock != null, "Need a block with which to construct the debug view.");
+ _joinBlock = joinBlock;
+ _sourceDebuggingInformation = joinBlock._source.GetDebuggingInformation();
+ }
+
+ /// <summary>Gets the messages waiting to be received.</summary>
+ public IEnumerable<Tuple<T1, T2, T3>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } }
+ /// <summary>Gets the number of joins created thus far.</summary>
+ public long JoinsCreated { get { return _joinBlock._sharedResources._joinsCreated; } }
+
+ /// <summary>Gets the task being used for input processing.</summary>
+ public Task TaskForInputProcessing { get { return _joinBlock._sharedResources._taskForInputProcessing; } }
+ /// <summary>Gets the task being used for output processing.</summary>
+ public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } }
+
+ /// <summary>Gets the GroupingDataflowBlockOptions used to configure this block.</summary>
+ public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ public bool IsDecliningPermanently { get { return _joinBlock._sharedResources._decliningPermanently; } }
+ /// <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(_joinBlock); } }
+
+ /// <summary>Gets the first target.</summary>
+ public ITargetBlock<T1> Target1 { get { return _joinBlock._target1; } }
+ /// <summary>Gets the second target.</summary>
+ public ITargetBlock<T2> Target2 { get { return _joinBlock._target2; } }
+ /// <summary>Gets the third target.</summary>
+ public ITargetBlock<T3> Target3 { get { return _joinBlock._target3; } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<Tuple<T1, T2, T3>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public ITargetBlock<Tuple<T1, T2, T3>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
+ }
+ }
+}
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Provides the target used in a Join.</summary>
+ /// <typeparam name="T">Specifies the type of data accepted by this target.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(JoinBlockTarget<>.DebugView))]
+ internal sealed class JoinBlockTarget<T> : JoinBlockTargetBase, ITargetBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>The shared resources used by all targets associated with the same join instance.</summary>
+ private readonly JoinBlockTargetSharedResources _sharedResources;
+ /// <summary>A task representing the completion of the block.</summary>
+ private readonly TaskCompletionSource<VoidResult> _completionTask = new TaskCompletionSource<VoidResult>();
+ /// <summary>Input messages for the next batch.</summary>
+ private readonly Queue<T> _messages;
+ /// <summary>State used when in non-greedy mode.</summary>
+ private readonly NonGreedyState _nonGreedy;
+ /// <summary>Whether this target is declining future messages.</summary>
+ private bool _decliningPermanently;
+
+ /// <summary>State used only when in non-greedy mode.</summary>
+ private sealed class NonGreedyState
+ {
+ /// <summary>Collection of the last postponed message per source.</summary>
+ internal readonly QueuedMap<ISourceBlock<T>, DataflowMessageHeader> PostponedMessages = new QueuedMap<ISourceBlock<T>, DataflowMessageHeader>();
+ /// <summary>The currently reserved message.</summary>
+ internal KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> ReservedMessage;
+ /// <summary>The currently consumed message.</summary>
+ internal KeyValuePair<bool, T> ConsumedMessage;
+ }
+
+ /// <summary>Initializes the target.</summary>
+ /// <param name="sharedResources">The shared resources used by all targets associated with this join.</param>
+ internal JoinBlockTarget(JoinBlockTargetSharedResources sharedResources)
+ {
+ Contract.Requires(sharedResources != null, "Targets need shared resources through which to communicate.");
+
+ // Store arguments and initialize configuration
+ GroupingDataflowBlockOptions dbo = sharedResources._dataflowBlockOptions;
+ _sharedResources = sharedResources;
+ if (!dbo.Greedy || dbo.BoundedCapacity > 0) _nonGreedy = new NonGreedyState();
+ if (dbo.Greedy) _messages = new Queue<T>();
+ }
+
+ /// <summary>Gets a message buffered by this target.</summary>
+ /// <remarks>This must be called while holding the shared Resources's incoming lock.</remarks>
+ internal T GetOneMessage()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+ if (_sharedResources._dataflowBlockOptions.Greedy)
+ {
+ Debug.Assert(_messages != null, "_messages must have been initialized in greedy mode");
+ Debug.Assert(_messages.Count >= 0, "A message must have been consumed by this point.");
+ return _messages.Dequeue();
+ }
+ else
+ {
+ Debug.Assert(_nonGreedy.ConsumedMessage.Key, "A message must have been consumed by this point.");
+ T value = _nonGreedy.ConsumedMessage.Value;
+ _nonGreedy.ConsumedMessage = new KeyValuePair<bool, T>(false, default(T));
+ return value;
+ }
+ }
+
+ /// <summary>Gets whether the target is declining messages.</summary>
+ internal override bool IsDecliningPermanently
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+ return _decliningPermanently;
+ }
+ }
+
+ /// <summary>Gets whether the target has at least one message available.</summary>
+ internal override bool HasAtLeastOneMessageAvailable
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+ if (_sharedResources._dataflowBlockOptions.Greedy)
+ {
+ Debug.Assert(_messages != null, "_messages must have been initialized in greedy mode");
+ return _messages.Count > 0;
+ }
+ else
+ {
+ return _nonGreedy.ConsumedMessage.Key;
+ }
+ }
+ }
+
+ /// <summary>Gets whether the target has at least one postponed message.</summary>
+ internal override bool HasAtLeastOnePostponedMessage
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+ return _nonGreedy != null && _nonGreedy.PostponedMessages.Count > 0;
+ }
+ }
+
+ /// <summary>Gets the number of messages available or postponed.</summary>
+ internal override int NumberOfMessagesAvailableOrPostponed
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+ return !_sharedResources._dataflowBlockOptions.Greedy ? _nonGreedy.PostponedMessages.Count : _messages.Count;
+ }
+ }
+
+ /// <summary>Gets whether this target has the highest number of available/buffered messages. This is only valid in greedy mode.</summary>
+ internal override bool HasTheHighestNumberOfMessagesAvailable
+ {
+ get
+ {
+ Debug.Assert(_sharedResources._dataflowBlockOptions.Greedy, "This is only valid in greedy mode");
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+
+ // Note: If there is a tie, we must return true
+ int count = _messages.Count;
+ foreach (JoinBlockTargetBase target in _sharedResources._targets)
+ if (target != this && target.NumberOfMessagesAvailableOrPostponed > count)
+ return false; // Strictly bigger!
+ return true;
+ }
+ }
+
+ /// <summary>Reserves one of the postponed messages.</summary>
+ /// <returns>true if a message was reserved; otherwise, false.</returns>
+ internal override bool ReserveOneMessage()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false);
+ Debug.Assert(!_sharedResources._dataflowBlockOptions.Greedy, "This is only used in non-greedy mode");
+
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> next;
+
+ lock (_sharedResources.IncomingLock)
+ {
+ // The queue must be empty between joins in non-greedy mode
+ Debug.Assert(!HasAtLeastOneMessageAvailable, "The queue must be empty between joins in non-greedy mode");
+
+ // While we are holding the lock, try to pop a postponed message.
+ // If there are no postponed messages, we can't do anything.
+ if (!_nonGreedy.PostponedMessages.TryPop(out next)) return false;
+ }
+
+ // We'll bail out of this loop either when we have reserved a message (true)
+ // or when we have exhausted the list of postponed messages (false)
+ for (; ;)
+ {
+ // Try to reserve the popped message
+ if (next.Key.ReserveMessage(next.Value, this))
+ {
+ _nonGreedy.ReservedMessage = next;
+ return true;
+ }
+
+ // We could not reserve that message.
+ // Try to pop another postponed message and continue looping.
+ lock (_sharedResources.IncomingLock)
+ {
+ // If there are no postponed messages, we can't do anything
+ if (!_nonGreedy.PostponedMessages.TryPop(out next)) return false;
+ }
+ }
+ }
+
+ /// <summary>Consumes a reserved message.</summary>
+ /// <returns>true if a message was consumed; otherwise, false.</returns>
+ internal override bool ConsumeReservedMessage()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false);
+ Debug.Assert(!_sharedResources._dataflowBlockOptions.Greedy, "This is only used in non-greedy mode");
+ Debug.Assert(_nonGreedy.ReservedMessage.Key != null, "This target must have a reserved message");
+
+ bool consumed;
+ T consumedValue = _nonGreedy.ReservedMessage.Key.ConsumeMessage(_nonGreedy.ReservedMessage.Value, this, out consumed);
+
+ // Null out our reservation
+ _nonGreedy.ReservedMessage = default(KeyValuePair<ISourceBlock<T>, DataflowMessageHeader>);
+
+ // The protocol requires that a reserved message must be consumable,
+ // but it is possible that the source may misbehave.
+ // In that case complete the target and signal to the owning block to shut down gracefully.
+ if (!consumed)
+ {
+ _sharedResources._exceptionAction(new InvalidOperationException(SR.InvalidOperation_FailedToConsumeReservedMessage));
+
+ // Complete this target, which will trigger completion of the owning join block.
+ CompleteOncePossible();
+
+ // We need to signal to the caller to stop consuming immediately
+ return false;
+ }
+ else
+ {
+ lock (_sharedResources.IncomingLock)
+ {
+ // Now that we've consumed it, store its data.
+ Debug.Assert(!_nonGreedy.ConsumedMessage.Key, "There must be no other consumed message");
+ _nonGreedy.ConsumedMessage = new KeyValuePair<bool, T>(true, consumedValue);
+ // We don't account bounding per target in non-greedy mode. We do it once per batch (in the loop).
+
+ CompleteIfLastJoinIsFeasible();
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>Consumes up to one postponed message in greedy bounded mode.</summary>
+ internal override bool ConsumeOnePostponedMessage()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false);
+ Debug.Assert(_sharedResources._dataflowBlockOptions.Greedy, "This is only used in greedy mode");
+ Debug.Assert(_sharedResources._boundingState != null, "This is only used in bounding mode");
+
+ // We'll bail out of this loop either when we have consumed a message (true)
+ // or when we have exhausted the list of postponed messages (false)
+ while (true)
+ {
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> next;
+ bool hasTheHighestNumberOfMessagesAvailable;
+
+ lock (_sharedResources.IncomingLock)
+ {
+ // While we are holding the lock, check bounding capacity and try to pop a postponed message.
+ // If anything fails, we can't do anything.
+ hasTheHighestNumberOfMessagesAvailable = HasTheHighestNumberOfMessagesAvailable;
+ bool boundingCapacityAvailable = _sharedResources._boundingState.CountIsLessThanBound || !hasTheHighestNumberOfMessagesAvailable;
+ if (_decliningPermanently || _sharedResources._decliningPermanently ||
+ !boundingCapacityAvailable || !_nonGreedy.PostponedMessages.TryPop(out next))
+ return false;
+ }
+
+ // Try to consume the popped message
+ bool consumed;
+ T consumedValue = next.Key.ConsumeMessage(next.Value, this, out consumed);
+ if (consumed)
+ {
+ lock (_sharedResources.IncomingLock)
+ {
+ // The ranking in highest number of available messages cannot have changed because this task is causing OfferMessage to postpone
+ if (hasTheHighestNumberOfMessagesAvailable) _sharedResources._boundingState.CurrentCount += 1; // track this new item against our bound
+ _messages.Enqueue(consumedValue);
+
+ CompleteIfLastJoinIsFeasible();
+ return true;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Start declining if the number of joins we've already made plus the number we can
+ /// make from data already enqueued meets our quota.
+ /// </summary>
+ private void CompleteIfLastJoinIsFeasible()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true);
+ int messageCount = _sharedResources._dataflowBlockOptions.Greedy ?
+ _messages.Count :
+ _nonGreedy.ConsumedMessage.Key ? 1 : 0;
+ if ((_sharedResources._joinsCreated + messageCount) >= _sharedResources._dataflowBlockOptions.ActualMaxNumberOfGroups)
+ {
+ _decliningPermanently = true;
+
+ bool allAreDecliningPermanently = true;
+ foreach (JoinBlockTargetBase target in _sharedResources._targets)
+ {
+ if (!target.IsDecliningPermanently)
+ {
+ allAreDecliningPermanently = false;
+ break;
+ }
+ }
+ if (allAreDecliningPermanently) _sharedResources._decliningPermanently = true;
+ }
+ }
+
+ /// <summary>Releases the reservation on a reserved message.</summary>
+ internal override void ReleaseReservedMessage()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false);
+
+ // Release only if we have a reserved message.
+ // Otherwise do nothing.
+ if (_nonGreedy != null && _nonGreedy.ReservedMessage.Key != null)
+ {
+ // Release the reservation and null out our reservation flag even if an exception occurs
+ try { _nonGreedy.ReservedMessage.Key.ReleaseReservation(_nonGreedy.ReservedMessage.Value, this); }
+ finally { ClearReservation(); }
+ }
+ }
+
+ /// <summary>Unconditionally clears a reserved message.</summary>
+ internal override void ClearReservation()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false);
+ Debug.Assert(_nonGreedy != null, "Only valid in non-greedy mode.");
+
+ _nonGreedy.ReservedMessage = default(KeyValuePair<ISourceBlock<T>, DataflowMessageHeader>);
+ }
+
+ /// <summary>Completes the target.</summary>
+ internal override void CompleteOncePossible()
+ {
+ Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false);
+
+ // This target must not have an outstanding reservation
+ Debug.Assert(_nonGreedy == null || _nonGreedy.ReservedMessage.Key == null,
+ "Must be in greedy mode, or in non-greedy mode but without any reserved messages.");
+
+ // Clean up any messages that may be stragglers left behind
+ lock (_sharedResources.IncomingLock)
+ {
+ _decliningPermanently = true;
+ if (_messages != null) _messages.Clear();
+ }
+
+ // Release any postponed messages
+ List<Exception> exceptions = null;
+ if (_nonGreedy != null)
+ {
+ // Note: No locks should be held at this point
+ Common.ReleaseAllPostponedMessages(this, _nonGreedy.PostponedMessages, ref exceptions);
+ }
+
+ if (exceptions != null)
+ {
+ // It is important to migrate these exceptions to the source part of the owning join,
+ // because that is the completion task that is publically exposed.
+ foreach (Exception exc in exceptions)
+ {
+ _sharedResources._exceptionAction(exc);
+ }
+ }
+
+ // Targets' completion tasks are only available internally with the sole purpose
+ // of releasing the task that completes the parent. Hence the actual reason
+ // for completing this task doesn't matter.
+ _completionTask.TrySetResult(default(VoidResult));
+ }
+
+ /// <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 (_sharedResources.IncomingLock)
+ {
+ // If we shouldn't be accepting more messages, don't.
+ if (_decliningPermanently || _sharedResources._decliningPermanently)
+ {
+ _sharedResources.CompleteBlockIfPossible();
+ return DataflowMessageStatus.DecliningPermanently;
+ }
+
+ // We can directly accept the message if:
+ // 1) we are being greedy AND we are not bounding, OR
+ // 2) we are being greedy AND 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 (_sharedResources._dataflowBlockOptions.Greedy &&
+ (_sharedResources._boundingState == null
+ ||
+ ((_sharedResources._boundingState.CountIsLessThanBound || !HasTheHighestNumberOfMessagesAvailable) &&
+ _nonGreedy.PostponedMessages.Count == 0 && _sharedResources._taskForInputProcessing == null)))
+ {
+ 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;
+ }
+ if (_sharedResources._boundingState != null && HasTheHighestNumberOfMessagesAvailable) _sharedResources._boundingState.CurrentCount += 1; // track this new item against our bound
+ _messages.Enqueue(messageValue);
+ CompleteIfLastJoinIsFeasible();
+
+ // Since we're in greedy mode, we can skip asynchronous processing and
+ // make joins aggressively based on enqueued data.
+ if (_sharedResources.AllTargetsHaveAtLeastOneMessage)
+ {
+ _sharedResources._joinFilledAction();
+ _sharedResources._joinsCreated++;
+ }
+
+ _sharedResources.CompleteBlockIfPossible();
+ return DataflowMessageStatus.Accepted;
+ }
+ // Otherwise, we try to postpone if a source was provided
+ else if (source != null)
+ {
+ Debug.Assert(_nonGreedy != null, "_nonGreedy must have been initialized during construction in non-greedy mode.");
+
+ // Postpone the message now and kick off an async two-phase consumption.
+ _nonGreedy.PostponedMessages.Push(source, messageHeader);
+ _sharedResources.ProcessAsyncIfNecessary();
+ return DataflowMessageStatus.Postponed;
+ }
+ // We can't do anything else about this message
+ return DataflowMessageStatus.Declined;
+ }
+ }
+
+ /// <summary>Completes/faults the block.
+ /// In general, it is not safe to pass releaseReservedMessages:true, because releasing of reserved messages
+ /// is done without taking a lock. We pass releaseReservedMessages:true only when an exception has been
+ /// caught inside the message processing loop which is a single instance at any given moment.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal override void CompleteCore(Exception exception, bool dropPendingMessages, bool releaseReservedMessages)
+ {
+ bool greedy = _sharedResources._dataflowBlockOptions.Greedy;
+ lock (_sharedResources.IncomingLock)
+ {
+ // Faulting from outside is allowed until we start declining permanently.
+ // Faulting from inside is allowed at any time.
+ if (exception != null && ((!_decliningPermanently && !_sharedResources._decliningPermanently) || releaseReservedMessages))
+ {
+ _sharedResources._exceptionAction(exception);
+ }
+
+ // Drop pending messages if requested
+ if (dropPendingMessages && greedy)
+ {
+ Debug.Assert(_messages != null, "_messages must be initialized in greedy mode.");
+ _messages.Clear();
+ }
+ }
+
+ // Release reserved messages if requested.
+ // This must be done from outside the lock.
+ if (releaseReservedMessages && !greedy)
+ {
+ // Do this on all targets
+ foreach (JoinBlockTargetBase target in _sharedResources._targets)
+ {
+ try { target.ReleaseReservedMessage(); }
+ catch (Exception e) { _sharedResources._exceptionAction(e); }
+ }
+ }
+
+ // Triggering completion requires the lock
+ lock (_sharedResources.IncomingLock)
+ {
+ // Trigger completion
+ _decliningPermanently = true;
+ _sharedResources.CompleteBlockIfPossible();
+ }
+ }
+
+ /// <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, dropPendingMessages: true, releaseReservedMessages: false);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } }
+ /// <summary>The completion task on Join targets is only hidden from the public. It still exists for internal purposes.</summary>
+ internal Task CompletionTaskInternal { get { return _completionTask.Task; } }
+
+ /// <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 _messages != null ? _messages.Count : _nonGreedy.ConsumedMessage.Key ? 1 : 0; } }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayJoin = _sharedResources._ownerJoin as IDebuggerDisplay;
+ return string.Format("{0} InputCount={1}, Join=\"{2}\"",
+ Common.GetNameForDebugger(this),
+ InputCountForDebugger,
+ displayJoin != null ? displayJoin.Content : _sharedResources._ownerJoin);
+ }
+ }
+ /// <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 Transform.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The join block target being viewed.</summary>
+ private readonly JoinBlockTarget<T> _joinBlockTarget;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="joinBlockTarget">The join being viewed.</param>
+ public DebugView(JoinBlockTarget<T> joinBlockTarget)
+ {
+ Contract.Requires(joinBlockTarget != null, "Need a target with which to construct the debug view.");
+ _joinBlockTarget = joinBlockTarget;
+ }
+
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ public IEnumerable<T> InputQueue { get { return _joinBlockTarget._messages; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ public bool IsDecliningPermanently { get { return _joinBlockTarget._decliningPermanently || _joinBlockTarget._sharedResources._decliningPermanently; } }
+ }
+ }
+
+ /// <summary>Provides a non-generic base type for all join targets.</summary>
+ internal abstract class JoinBlockTargetBase
+ {
+ /// <summary>Whether the target is postponing messages.</summary>
+ internal abstract bool IsDecliningPermanently { get; }
+ /// <summary>Whether the target has at least one message available.</summary>
+ internal abstract bool HasAtLeastOneMessageAvailable { get; }
+ /// <summary>Whether the target has at least one message postponed.</summary>
+ internal abstract bool HasAtLeastOnePostponedMessage { get; }
+ /// <summary>Gets the number of messages available or postponed.</summary>
+ internal abstract int NumberOfMessagesAvailableOrPostponed { get; }
+ /// <summary>Gets whether the target has the highest number of messages available. (A tie yields true.)</summary>
+ internal abstract bool HasTheHighestNumberOfMessagesAvailable { get; }
+
+ /// <summary>Reserves a single message.</summary>
+ /// <returns>Whether a message was reserved.</returns>
+ internal abstract bool ReserveOneMessage();
+ /// <summary>Consumes any previously reserved message.</summary>
+ /// <returns>Whether a message was consumed.</returns>
+ internal abstract bool ConsumeReservedMessage();
+ /// <summary>Consumes up to one postponed message in greedy bounded mode.</summary>
+ /// <returns>Whether a message was consumed.</returns>
+ internal abstract bool ConsumeOnePostponedMessage();
+ /// <summary>Releases any previously reserved message.</summary>
+ internal abstract void ReleaseReservedMessage();
+ /// <summary>Unconditionally clears a reserved message. This is only invoked in case of an exception.</summary>
+ internal abstract void ClearReservation();
+
+ /// <summary>Access point to the corresponding API method.</summary>
+ public void Complete() { CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); }
+ /// <summary>Internal implementation of the corresponding API method.</summary>
+ internal abstract void CompleteCore(Exception exception, bool dropPendingMessages, bool releaseReservedMessages);
+ /// <summary>Completes the target.</summary>
+ internal abstract void CompleteOncePossible();
+ }
+
+ /// <summary>Provides a container for resources shared across all targets used by the same BatchedJoin instance.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ internal sealed class JoinBlockTargetSharedResources
+ {
+ /// <summary>Initializes the shared resources.</summary>
+ /// <param name="ownerJoin">
+ /// The join block that owns these shared resources.
+ /// </param>
+ /// <param name="targets">
+ /// The array of targets associated with the join. Also doubles as the sync object used to synchronize targets.
+ /// </param>
+ /// <param name="joinFilledAction">The delegate to invoke when enough messages have been consumed to fulfill the join.</param>
+ /// <param name="exceptionAction">The delegate to invoke when a target encounters an error.</param>
+ /// <param name="dataflowBlockOptions">The options for the join.</param>
+ internal JoinBlockTargetSharedResources(
+ IDataflowBlock ownerJoin, JoinBlockTargetBase[] targets,
+ Action joinFilledAction, Action<Exception> exceptionAction,
+ GroupingDataflowBlockOptions dataflowBlockOptions)
+ {
+ Contract.Requires(ownerJoin != null, "Resources must be associated with a join.");
+ Contract.Requires(targets != null, "Resources must be shared between multiple targets.");
+ Contract.Requires(joinFilledAction != null, "An action to invoke when a join is created must be provided.");
+ Contract.Requires(exceptionAction != null, "An action to invoke for faults must be provided.");
+ Contract.Requires(dataflowBlockOptions != null, "Options must be provided to configure the resources.");
+
+ // Store arguments
+ _ownerJoin = ownerJoin;
+ _targets = targets;
+ _joinFilledAction = joinFilledAction;
+ _exceptionAction = exceptionAction;
+ _dataflowBlockOptions = dataflowBlockOptions;
+
+ // Initialize bounding state if necessary
+ if (dataflowBlockOptions.BoundedCapacity > 0) _boundingState = new BoundingState(dataflowBlockOptions.BoundedCapacity);
+ }
+
+ // *** Accessible fields and properties
+ internal readonly IDataflowBlock _ownerJoin;
+ /// <summary>All of the targets associated with the join.</summary>
+ internal readonly JoinBlockTargetBase[] _targets;
+ /// <summary>The delegate to invoke when a target encounters an error.</summary>
+ internal readonly Action<Exception> _exceptionAction;
+ /// <summary>The delegate to invoke when enough messages have been consumed to fulfill the join.</summary>
+ internal readonly Action _joinFilledAction;
+ /// <summary>The options for the join.</summary>
+ internal readonly GroupingDataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>Bounding state for when the block is executing in bounded mode.</summary>
+ internal readonly BoundingState _boundingState;
+ /// <summary>Whether all targets should decline all further messages.</summary>
+ internal bool _decliningPermanently;
+ /// <summary>The task used to process messages.</summary>
+ internal Task _taskForInputProcessing;
+ /// <summary>Whether any exceptions have been generated and stored into the source core.</summary>
+ internal bool _hasExceptions;
+ /// <summary>The number of joins this block has created.</summary>
+ internal long _joinsCreated;
+
+ // *** Private fields and properties - mutatable
+ /// <summary>A task has reserved the right to run the completion routine.</summary>
+ private bool _completionReserved;
+
+ /// <summary>Gets the lock used to synchronize all incoming messages on all targets.</summary>
+ internal object IncomingLock { get { return _targets; } }
+
+ /// <summary>Invokes Complete on each target with dropping buffered messages.</summary>
+ internal void CompleteEachTarget()
+ {
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ target.CompleteCore(exception: null, dropPendingMessages: true, releaseReservedMessages: false);
+ }
+ }
+
+ /// <summary>Gets whether all of the targets have at least one message in their queues.</summary>
+ internal bool AllTargetsHaveAtLeastOneMessage
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ if (!target.HasAtLeastOneMessageAvailable) return false;
+ }
+ return true;
+ }
+ }
+
+ /// <summary>Gets whether all of the targets have at least one message in their queues or have at least one postponed message.</summary>
+ private bool TargetsHaveAtLeastOneMessageQueuedOrPostponed
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (_boundingState == null)
+ {
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ if (!target.HasAtLeastOneMessageAvailable &&
+ (_decliningPermanently || target.IsDecliningPermanently || !target.HasAtLeastOnePostponedMessage))
+ return false;
+ }
+ return true;
+ }
+ else
+ {
+ // Cache the availability state so we don't evaluate it multiple times
+ bool boundingCapacityAvailable = _boundingState.CountIsLessThanBound;
+
+ // In bounding mode, we have more complex rules whether we should process input messages:
+ // 1) In greedy mode if a target has postponed messages and there is bounding capacity
+ // available, then we should greedily consume messages up to the bounding capacity
+ // even if that doesn't lead to an output join.
+ // 2) The ability to make join depends on:
+ // 2a) message availability for each target, AND
+ // 2b) availability of bounding space
+
+ bool joinIsPossible = true;
+ bool joinWillNotAffectBoundingCount = false;
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ bool targetCanConsumePostponedMessages = !_decliningPermanently && !target.IsDecliningPermanently && target.HasAtLeastOnePostponedMessage;
+
+ // Rule #1
+ if (_dataflowBlockOptions.Greedy && targetCanConsumePostponedMessages && (boundingCapacityAvailable || !target.HasTheHighestNumberOfMessagesAvailable)) return true;
+
+ // Rule #2a
+ bool targetHasMessagesAvailable = target.HasAtLeastOneMessageAvailable;
+ joinIsPossible &= targetHasMessagesAvailable || targetCanConsumePostponedMessages;
+
+ // Rule #2b
+ // If there is a target that has at least one queued message, bounding space availability
+ // is no longer an issue, because 1 item from the input side will be replaced with 1
+ // item on the output side.
+ if (targetHasMessagesAvailable) joinWillNotAffectBoundingCount = true;
+ }
+
+ // Rule #2
+ return joinIsPossible && (joinWillNotAffectBoundingCount || boundingCapacityAvailable);
+ }
+ }
+ }
+
+ /// <summary>Retrieves postponed items if we have enough to make a batch.</summary>
+ /// <returns>true if input messages for a batch were consumed (all or none); false otherwise.</returns>
+ private bool RetrievePostponedItemsNonGreedy()
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // If there are not enough postponed items, we have nothing to do.
+ lock (IncomingLock)
+ {
+ if (!TargetsHaveAtLeastOneMessageQueuedOrPostponed) return false;
+ } // Release the lock. We must not hold it while calling Reserve/Consume/Release.
+
+ // Try to reserve a postponed message on every target that doesn't already have messages available
+ bool reservedAll = true;
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ if (!target.ReserveOneMessage())
+ {
+ reservedAll = false;
+ break;
+ }
+ }
+
+ // If we were able to, consume them all and place the consumed messages into each's queue
+ if (reservedAll)
+ {
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ // If we couldn't consume a message, release reservations wherever possible
+ if (!target.ConsumeReservedMessage())
+ {
+ reservedAll = false;
+ break;
+ }
+ }
+ }
+
+ // If we were unable to reserve all messages, release the reservations
+ if (!reservedAll)
+ {
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ target.ReleaseReservedMessage();
+ }
+ }
+
+ return reservedAll;
+ }
+
+ /// <summary>Retrieves up to one postponed item through each target.</summary>
+ /// <returns>true if at least one input message was consumed (through any target); false otherwise.</returns>
+ private bool RetrievePostponedItemsGreedyBounded()
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // Try to consume a postponed message through each target as possible
+ bool consumed = false;
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ // It is sufficient to consume through one target to consider we've made progress
+ consumed |= target.ConsumeOnePostponedMessage();
+ }
+
+ return consumed;
+ }
+
+ /// <summary>Gets whether the target has had cancellation requested or an exception has occurred.</summary>
+ private bool CanceledOrFaulted
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+ return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || _hasExceptions;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the join is in a state where processing can be done, meaning there's data
+ /// to be processed and the block is in a state where such processing is allowed.
+ /// </summary>
+ internal bool JoinNeedsProcessing
+ {
+ get
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+ return
+ _taskForInputProcessing == null && // not currently processing asynchronously
+ !CanceledOrFaulted && // not canceled or faulted
+ TargetsHaveAtLeastOneMessageQueuedOrPostponed; // all targets have work queued or postponed
+ }
+ }
+
+ /// <summary>Called when new messages are available to be processed.</summary>
+ /// <param name="isReplacementReplica">Whether this call is the continuation of a previous message loop.</param>
+ internal void ProcessAsyncIfNecessary(bool isReplacementReplica = false)
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (JoinNeedsProcessing)
+ {
+ ProcessAsyncIfNecessary_Slow(isReplacementReplica);
+ }
+ }
+
+ /// <summary>
+ /// Slow path for ProcessAsyncIfNecessary.
+ /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined.
+ /// </summary>
+ private void ProcessAsyncIfNecessary_Slow(bool isReplacementReplica)
+ {
+ Contract.Requires(JoinNeedsProcessing, "There must be a join that needs processing.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ // Create task and store into _taskForInputProcessing prior to scheduling the task
+ // so that _taskForInputProcessing will be visibly set in the task loop.
+ _taskForInputProcessing = new Task(thisSharedResources => ((JoinBlockTargetSharedResources)thisSharedResources).ProcessMessagesLoopCore(), this,
+ Common.GetCreationOptionsForTask(isReplacementReplica));
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ _ownerJoin, _taskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages,
+ _targets.Max(t => t.NumberOfMessagesAvailableOrPostponed));
+ }
+#endif
+
+ // Start the task handling scheduling exceptions
+ Exception exception = Common.StartTaskSafe(_taskForInputProcessing, _dataflowBlockOptions.TaskScheduler);
+ if (exception != null)
+ {
+ // All of the following actions must be performed under the lock.
+ // So do them now while the lock is being held.
+
+ // First, log the exception while the processing state is dirty which is preventing the block from completing.
+ // Then revert the proactive processing state changes.
+ // And last, try to complete the block.
+ _exceptionAction(exception);
+ _taskForInputProcessing = null;
+ CompleteBlockIfPossible();
+ }
+ }
+
+ /// <summary>Completes the join block if possible.</summary>
+ internal void CompleteBlockIfPossible()
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (!_completionReserved)
+ {
+ // Check whether we're sure we'll never be able to fill another join.
+ // That could happen if we're not accepting more messages and not all targets have a message...
+ bool impossibleToCompleteAnotherJoin = _decliningPermanently && !AllTargetsHaveAtLeastOneMessage;
+ if (!impossibleToCompleteAnotherJoin)
+ {
+ //...or that could happen if an individual target isn't accepting messages and doesn't have any messages available
+ foreach (JoinBlockTargetBase target in _targets)
+ {
+ if (target.IsDecliningPermanently && !target.HasAtLeastOneMessageAvailable)
+ {
+ impossibleToCompleteAnotherJoin = true;
+ break;
+ }
+ }
+ }
+
+ // We're done forever if there's no task currently processing and
+ // either it's impossible we'll have another join or we're canceled.
+ bool currentlyProcessing = _taskForInputProcessing != null;
+ bool shouldComplete = !currentlyProcessing && (impossibleToCompleteAnotherJoin || CanceledOrFaulted);
+
+ if (shouldComplete)
+ {
+ // Make sure no one else tries to call CompleteBlockOncePossible
+ _completionReserved = true;
+
+ // Make sure all targets are declining
+ _decliningPermanently = true;
+
+ // Complete each target asynchronously so as not to invoke synchronous continuations under a lock
+ Task.Factory.StartNew(state =>
+ {
+ var sharedResources = (JoinBlockTargetSharedResources)state;
+ foreach (JoinBlockTargetBase target in sharedResources._targets) target.CompleteOncePossible();
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+ }
+
+ /// <summary>Task body used to process messages.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void ProcessMessagesLoopCore()
+ {
+ Contract.Requires(!_dataflowBlockOptions.Greedy || _boundingState != null, "This only makes sense in non-greedy or bounding mode");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+ try
+ {
+ int timesThroughLoop = 0;
+ int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask;
+ bool madeProgress;
+ do
+ {
+ // Retrieve postponed messages.
+ // In greedy bounded mode, consuming a message through a target is sufficient
+ // to consider we've made progress, i.e. to stay in the loop.
+ madeProgress = !_dataflowBlockOptions.Greedy ?
+ RetrievePostponedItemsNonGreedy() :
+ RetrievePostponedItemsGreedyBounded();
+
+ if (madeProgress)
+ {
+ // Convert buffered messages into a filled join if each target has at least one buffered message
+ lock (IncomingLock)
+ {
+ if (AllTargetsHaveAtLeastOneMessage)
+ {
+ _joinFilledAction(); // Pluck a message from each target
+ _joinsCreated++;
+
+ // If we are in non-greedy mode, do this once per join
+ if (!_dataflowBlockOptions.Greedy && _boundingState != null) _boundingState.CurrentCount += 1;
+ }
+ }
+ }
+
+ timesThroughLoop++;
+ } while (madeProgress && timesThroughLoop < maxMessagesPerTask);
+ }
+ catch (Exception exception)
+ {
+ // We can trigger completion of the JoinBlock by completing one target.
+ // It doesn't matter which one. So we always complete the first one.
+ Debug.Assert(_targets.Length > 0, "A join must have targets.");
+ _targets[0].CompleteCore(exception, dropPendingMessages: true, releaseReservedMessages: true);
+ // The finally section will do the block completion.
+ }
+ finally
+ {
+ lock (IncomingLock)
+ {
+ // We're no longer processing, so null out the processing task
+ _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.
+ ProcessAsyncIfNecessary(isReplacementReplica: true);
+
+ // If, however, we stopped because we ran out of work to do and we
+ // know we'll never get more, then complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <summary>Notifies the block that one or more items was removed from the queue.</summary>
+ /// <param name="numItemsRemoved">The number of items removed.</param>
+ internal void OnItemsRemoved(int numItemsRemoved)
+ {
+ Contract.Requires(numItemsRemoved > 0, "Number of items removed needs to be positive.");
+ 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;
+
+ ProcessAsyncIfNecessary();
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <summary>Gets the object to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayJoin = _ownerJoin as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displayJoin != null ? displayJoin.Content : _ownerJoin);
+ }
+ }
+ }
+}
--- /dev/null
+// 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; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// TransformManyBlock.cs
+//
+//
+// A propagator block that runs a function on each input to produce zero or more outputs.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Threading.Tasks.Dataflow.Internal;
+using System.Collections.ObjectModel;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Provides a dataflow block that invokes a provided <see cref="System.Func{T,TResult}"/> delegate for every data element received.</summary>
+ /// <typeparam name="TInput">Specifies the type of data received and operated on by this <see cref="TransformManyBlock{TInput,TOutput}"/>.</typeparam>
+ /// <typeparam name="TOutput">Specifies the type of data output by this <see cref="TransformManyBlock{TInput,TOutput}"/>.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(TransformManyBlock<,>.DebugView))]
+ public sealed class TransformManyBlock<TInput, TOutput> : IPropagatorBlock<TInput, TOutput>, IReceivableSourceBlock<TOutput>, IDebuggerDisplay
+ {
+ /// <summary>The target side.</summary>
+ private readonly TargetCore<TInput> _target;
+ /// <summary>
+ /// Buffer used to reorder output sets that may have completed out-of-order between the target half and the source half.
+ /// This specialized reordering buffer supports streaming out enumerables if the message is the next in line.
+ /// </summary>
+ private readonly ReorderingBuffer<IEnumerable<TOutput>> _reorderingBuffer;
+ /// <summary>The source side.</summary>
+ private readonly SourceCore<TOutput> _source;
+
+ /// <summary>Initializes the <see cref="TransformManyBlock{TInput,TOutput}"/> with the specified function.</summary>
+ /// <param name="transform">
+ /// The function to invoke with each data element received. All of the data from the returned <see cref="System.Collections.Generic.IEnumerable{TOutput}"/>
+ /// will be made available as output from this <see cref="TransformManyBlock{TInput,TOutput}"/>.
+ /// </param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="transform"/> is null (Nothing in Visual Basic).</exception>
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public TransformManyBlock(Func<TInput, IEnumerable<TOutput>> transform) :
+ this(transform, null, ExecutionDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes the <see cref="TransformManyBlock{TInput,TOutput}"/> with the specified function and <see cref="ExecutionDataflowBlockOptions"/>.</summary>
+ /// <param name="transform">
+ /// The function to invoke with each data element received. All of the data from the returned in the <see cref="System.Collections.Generic.IEnumerable{TOutput}"/>
+ /// will be made available as output from this <see cref="TransformManyBlock{TInput,TOutput}"/>.
+ /// </param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="TransformManyBlock{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 TransformManyBlock(Func<TInput, IEnumerable<TOutput>> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) :
+ this(transform, null, dataflowBlockOptions)
+ { }
+
+ /// <summary>Initializes the <see cref="TransformManyBlock{TInput,TOutput}"/> with the specified function.</summary>
+ /// <param name="transform">
+ /// The function to invoke with each data element received. All of the data asynchronously returned in the <see cref="System.Collections.Generic.IEnumerable{TOutput}"/>
+ /// will be made available as output from this <see cref="TransformManyBlock{TInput,TOutput}"/>.
+ /// </param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="transform"/> is null (Nothing in Visual Basic).</exception>
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
+ public TransformManyBlock(Func<TInput, Task<IEnumerable<TOutput>>> transform) :
+ this(null, transform, ExecutionDataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes the <see cref="TransformManyBlock{TInput,TOutput}"/> with the specified function and <see cref="ExecutionDataflowBlockOptions"/>.</summary>
+ /// <param name="transform">
+ /// The function to invoke with each data element received. All of the data asynchronously returned in the <see cref="System.Collections.Generic.IEnumerable{TOutput}"/>
+ /// will be made available as output from this <see cref="TransformManyBlock{TInput,TOutput}"/>.
+ /// </param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="TransformManyBlock{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 TransformManyBlock(Func<TInput, Task<IEnumerable<TOutput>>> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) :
+ this(null, transform, dataflowBlockOptions)
+ { }
+
+ /// <summary>Initializes the <see cref="TransformManyBlock{TInput,TOutput}"/> with the specified function and <see cref="ExecutionDataflowBlockOptions"/>.</summary>
+ /// <param name="transformSync">The synchronous function to invoke with each data element received.</param>
+ /// <param name="transformAsync">The asynchronous function to invoke with each data element received.</param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="TransformManyBlock{TInput,TOutput}"/>.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="transformSync"/> and <paramref name="transformAsync"/> are both null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ private TransformManyBlock(Func<TInput, IEnumerable<TOutput>> transformSync, Func<TInput, Task<IEnumerable<TOutput>>> transformAsync, ExecutionDataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments. It's ok for the filterFunction to be null, but not the other parameters.
+ if (transformSync == null && transformAsync == null) throw new ArgumentNullException("transform");
+ if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions");
+
+ Contract.Requires(transformSync == null ^ transformAsync == null, "Exactly one of transformSync and transformAsync must be null.");
+ Contract.EndContractBlock();
+
+ // Ensure we have options that can't be changed by the caller
+ dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone();
+
+ // Initialize onItemsRemoved delegate if necessary
+ Action<ISourceBlock<TOutput>, int> onItemsRemoved = null;
+ if (dataflowBlockOptions.BoundedCapacity > 0)
+ onItemsRemoved = (owningSource, count) => ((TransformManyBlock<TInput, TOutput>)owningSource)._target.ChangeBoundingCount(-count);
+
+ // Initialize source component
+ _source = new SourceCore<TOutput>(this, dataflowBlockOptions,
+ owningSource => ((TransformManyBlock<TInput, TOutput>)owningSource)._target.Complete(exception: null, dropPendingMessages: true),
+ onItemsRemoved);
+
+ // If parallelism is employed, we will need to support reordering messages that complete out-of-order.
+ if (dataflowBlockOptions.SupportsParallelExecution)
+ {
+ _reorderingBuffer = new ReorderingBuffer<IEnumerable<TOutput>>(
+ this, (source, messages) => ((TransformManyBlock<TInput, TOutput>)source)._source.AddMessages(messages));
+ }
+
+ // Create the underlying target and source
+ if (transformSync != null) // sync
+ {
+ // If an enumerable function was provided, we can use synchronous completion, meaning
+ // that the target will consider a message fully processed as soon as the
+ // delegate returns.
+ _target = new TargetCore<TInput>(this,
+ messageWithId => ProcessMessage(transformSync, messageWithId),
+ _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.None);
+ }
+ else // async
+ {
+ Debug.Assert(transformAsync != null, "Incorrect delegate type.");
+
+ // If a task-based function was provided, we need to use asynchronous completion, meaning
+ // that the target won't consider a message completed until the task
+ // returned from that delegate has completed.
+ _target = new TargetCore<TInput>(this,
+ messageWithId => ProcessMessageWithTask(transformAsync, messageWithId),
+ _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.UsesAsyncCompletion);
+ }
+
+ // Link up the target half with the source half. In doing so,
+ // ensure exceptions are propagated, and let the source know no more messages will arrive.
+ // As the target has completed, and as the target synchronously pushes work
+ // through the reordering buffer when async processing completes,
+ // we know for certain that no more messages will need to be sent to the source.
+ _target.Completion.ContinueWith((completed, state) =>
+ {
+ var sourceCore = (SourceCore<TOutput>)state;
+ if (completed.IsFaulted) sourceCore.AddAndUnwrapAggregateException(completed.Exception);
+ sourceCore.Complete();
+ }, _source, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default);
+
+ // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception.
+ // In those cases we need to fault the target half to drop its buffered messages and to release its
+ // reservations. This should not create an infinite loop, because all our implementations are designed
+ // to handle multiple completion requests and to carry over only one.
+ _source.Completion.ContinueWith((completed, state) =>
+ {
+ var thisBlock = ((TransformManyBlock<TInput, TOutput>)state) as IDataflowBlock;
+ Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion.");
+ thisBlock.Fault(completed.Exception);
+ }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
+
+ // Handle async cancellation requests by declining on the target
+ Common.WireCancellationToComplete(
+ dataflowBlockOptions.CancellationToken, Completion, state => ((TargetCore<TInput>)state).Complete(exception: null, dropPendingMessages: true), _target);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <summary>Processes the message with a user-provided transform function that returns an enumerable.</summary>
+ /// <param name="transformFunction">The transform function to use to process the message.</param>
+ /// <param name="messageWithId">The message to be processed.</param>
+ private void ProcessMessage(Func<TInput, IEnumerable<TOutput>> transformFunction, KeyValuePair<TInput, long> messageWithId)
+ {
+ Contract.Requires(transformFunction != null, "Function to invoke is required.");
+
+ bool userDelegateSucceeded = false;
+ try
+ {
+ // Run the user transform and store the results.
+ IEnumerable<TOutput> outputItems = transformFunction(messageWithId.Key);
+ userDelegateSucceeded = true;
+ StoreOutputItems(messageWithId, outputItems);
+ }
+ catch (Exception exc)
+ {
+ // If this exception represents cancellation, swallow it rather than shutting down the block.
+ if (!Common.IsCooperativeCancellation(exc)) throw;
+ }
+ finally
+ {
+ // If the user delegate failed, store an empty set in order
+ // to update the bounding count and reordering buffer.
+ if (!userDelegateSucceeded) StoreOutputItems(messageWithId, null);
+ }
+ }
+
+ /// <summary>Processes the message with a user-provided transform function that returns an observable.</summary>
+ /// <param name="function">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<IEnumerable<TOutput>>> function, KeyValuePair<TInput, long> messageWithId)
+ {
+ Contract.Requires(function != null, "Function to invoke is required.");
+
+ // Run the transform function to get the resulting task
+ Task<IEnumerable<TOutput>> task = null;
+ Exception caughtException = null;
+ try
+ {
+ task = function(messageWithId.Key);
+ }
+ catch (Exception exc) { caughtException = exc; }
+
+ // If no task is available, either because null was returned or an exception was thrown, we're done.
+ if (task == null)
+ {
+ // If we didn't get a task because an exception occurred, store it
+ // (or 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);
+ }
+
+ // Notify that we're done with this input and that we got no output for the input.
+ if (_reorderingBuffer != null)
+ {
+ // If there's a reordering buffer, "store" an empty output. This will
+ // internally both update the output buffer and decrement the bounding count
+ // accordingly.
+ StoreOutputItems(messageWithId, null);
+ _target.SignalOneAsyncMessageCompleted();
+ }
+ else
+ {
+ // As a fast path if we're not reordering, decrement the bounding
+ // count as part of our signaling that we're done, since this will
+ // internally take the lock only once, whereas the above path will
+ // take the lock twice.
+ _target.SignalOneAsyncMessageCompleted(boundingCountChange: -1);
+ }
+ return;
+ }
+
+ // We got back a task. Now wait for it to complete and store its results.
+ // Unlike with TransformBlock and ActionBlock, We run the continuation on the user-provided
+ // scheduler as we'll be running user code through enumerating the returned enumerable.
+ task.ContinueWith((completed, state) =>
+ {
+ var tuple = (Tuple<TransformManyBlock<TInput, TOutput>, KeyValuePair<TInput, long>>)state;
+ tuple.Item1.AsyncCompleteProcessMessageWithTask(completed, tuple.Item2);
+ }, Tuple.Create(this, messageWithId),
+ CancellationToken.None,
+ Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously),
+ _source.DataflowBlockOptions.TaskScheduler);
+ }
+
+ /// <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>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void AsyncCompleteProcessMessageWithTask(
+ Task<IEnumerable<TOutput>> completed, KeyValuePair<TInput, long> messageWithId)
+ {
+ Contract.Requires(completed != null, "A task should have been provided.");
+ Contract.Requires(completed.IsCompleted, "The task should have been in a final state.");
+
+ switch (completed.Status)
+ {
+ case TaskStatus.RanToCompletion:
+ IEnumerable<TOutput> outputItems = completed.Result;
+ try
+ {
+ // Get the resulting enumerable and persist it.
+ StoreOutputItems(messageWithId, outputItems);
+ }
+ catch (Exception exc)
+ {
+ // Enumerating the user's collection failed. If this exception represents cancellation,
+ // swallow it rather than shutting down the block.
+ if (!Common.IsCooperativeCancellation(exc))
+ {
+ // The exception was not for cancellation. 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.
+ Common.StoreDataflowMessageValueIntoExceptionData(exc, messageWithId.Key);
+ _target.Complete(exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false);
+ }
+ }
+ 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);
+ goto case TaskStatus.Canceled;
+ case TaskStatus.Canceled:
+ StoreOutputItems(messageWithId, null); // notify the reordering buffer and decrement the bounding count
+ break;
+
+ default:
+ Debug.Assert(false, "The task should have been in a final state.");
+ break;
+ }
+
+ // Let the target know that one of the asynchronous operations it launched has completed.
+ _target.SignalOneAsyncMessageCompleted();
+ }
+
+ /// <summary>
+ /// Stores the output items, either into the reordering buffer or into the source half.
+ /// Ensures that the bounding count is correctly updated.
+ /// </summary>
+ /// <param name="messageWithId">The message with id.</param>
+ /// <param name="outputItems">The output items to be persisted.</param>
+ private void StoreOutputItems(
+ KeyValuePair<TInput, long> messageWithId, IEnumerable<TOutput> outputItems)
+ {
+ // If there's a reordering buffer, pass the data along to it.
+ // The reordering buffer will handle all details, including bounding.
+ if (_reorderingBuffer != null)
+ {
+ StoreOutputItemsReordered(messageWithId.Value, outputItems);
+ }
+ // Otherwise, output the data directly.
+ else if (outputItems != null)
+ {
+ // If this is a trusted type, output the data en mass.
+ if (outputItems is TOutput[] || outputItems is List<TOutput>)
+ {
+ StoreOutputItemsNonReorderedAtomic(outputItems);
+ }
+ else
+ {
+ // Otherwise, we need to take the slow path of enumerating
+ // each individual item.
+ StoreOutputItemsNonReorderedWithIteration(outputItems);
+ }
+ }
+ else if (_target.IsBounded)
+ {
+ // outputItems is null and there's no reordering buffer
+ // and we're bounding, so decrement the bounding count to
+ // signify that the input element we already accounted for
+ // produced no output
+ _target.ChangeBoundingCount(count: -1);
+ }
+ // else there's no reordering buffer, there are no output items, and we're not bounded,
+ // so there's nothing more to be done.
+ }
+
+ /// <summary>Stores the next item using the reordering buffer.</summary>
+ /// <param name="id">The ID of the item.</param>
+ /// <param name="item">The completed item.</param>
+ private void StoreOutputItemsReordered(long id, IEnumerable<TOutput> item)
+ {
+ Contract.Requires(_reorderingBuffer != null, "Expected a reordering buffer");
+ Contract.Requires(id != Common.INVALID_REORDERING_ID, "This ID should never have been handed out.");
+
+ // Grab info about the transform
+ TargetCore<TInput> target = _target;
+ bool isBounded = target.IsBounded;
+
+ // Handle invalid items (null enumerables) by delegating to the base
+ if (item == null)
+ {
+ _reorderingBuffer.AddItem(id, null, false);
+ if (isBounded) target.ChangeBoundingCount(count: -1);
+ return;
+ }
+
+ // If we can eagerly get the number of items in the collection, update the bounding count.
+ // This avoids the cost of updating it once per output item (since each update requires synchronization).
+ // Even if we're not bounding, we still want to determine whether the item is trusted so that we
+ // can immediately dump it out once we take the lock if we're the next item.
+ IList<TOutput> itemAsTrustedList = item as TOutput[];
+ if (itemAsTrustedList == null) itemAsTrustedList = item as List<TOutput>;
+ if (itemAsTrustedList != null && isBounded)
+ {
+ UpdateBoundingCountWithOutputCount(count: itemAsTrustedList.Count);
+ }
+
+ // Determine whether this id is the next item, and if it is and if we have a trusted list,
+ // try to output it immediately on the fast path. If it can be output, we're done.
+ // Otherwise, make forward progress based on whether we're next in line.
+ bool? isNextNullable = _reorderingBuffer.AddItemIfNextAndTrusted(id, itemAsTrustedList, itemAsTrustedList != null);
+ if (!isNextNullable.HasValue) return; // data was successfully output
+ bool isNextItem = isNextNullable.Value;
+
+ // By this point, either we're not the next item, in which case we need to make a copy of the
+ // data and store it, or we are the next item and can store it immediately but we need to enumerate
+ // the items and store them individually because we don't want to enumerate while holding a lock.
+ List<TOutput> itemCopy = null;
+ try
+ {
+ // If this is the next item, we can output it now.
+ if (isNextItem)
+ {
+ StoreOutputItemsNonReorderedWithIteration(item);
+ // here itemCopy remains null, so that base.AddItem will finish our interactions with the reordering buffer
+ }
+ else if (itemAsTrustedList != null)
+ {
+ itemCopy = itemAsTrustedList.ToList();
+ // we already got the count and updated the bounding count previously
+ }
+ else
+ {
+ // We're not the next item, and we're not trusted, so copy the data into a list.
+ // We need to enumerate outside of the lock in the base class.
+ int itemCount = 0;
+ try
+ {
+ itemCopy = item.ToList(); // itemCopy will remain null in the case of exception
+ itemCount = itemCopy.Count;
+ }
+ finally
+ {
+ // If we're here successfully, then itemCount is the number of output items
+ // we actually received, and we should update the bounding count with it.
+ // If we're here because ToList threw an exception, then itemCount will be 0,
+ // and we still need to update the bounding count with this in order to counteract
+ // the increased bounding count for the corresponding input.
+ if (isBounded) UpdateBoundingCountWithOutputCount(count: itemCount);
+ }
+ }
+ // else if the item isn't valid, the finally block will see itemCopy as null and output invalid
+ }
+ finally
+ {
+ // Tell the base reordering buffer that we're done. If we already output
+ // all of the data, itemCopy will be null, and we just pass down the invalid item.
+ // If we haven't, pass down the real thing. We do this even in the case of an exception,
+ // in which case this will be a dummy element.
+ _reorderingBuffer.AddItem(id, itemCopy, itemIsValid: itemCopy != null);
+ }
+ }
+
+ /// <summary>
+ /// Stores the trusted enumerable en mass into the source core.
+ /// This method does not go through the reordering buffer.
+ /// </summary>
+ /// <param name="outputItems"></param>
+ private void StoreOutputItemsNonReorderedAtomic(IEnumerable<TOutput> outputItems)
+ {
+ Contract.Requires(_reorderingBuffer == null, "Expected not to have a reordering buffer");
+ Contract.Requires(outputItems is TOutput[] || outputItems is List<TOutput>, "outputItems must be a list we've already vetted as trusted");
+ if (_target.IsBounded) UpdateBoundingCountWithOutputCount(count: ((ICollection<TOutput>)outputItems).Count);
+ _source.AddMessages(outputItems);
+ }
+
+ /// <summary>
+ /// Stores the untrusted enumerable into the source core.
+ /// This method does not go through the reordering buffer.
+ /// </summary>
+ /// <param name="outputItems">The untrusted enumerable.</param>
+ private void StoreOutputItemsNonReorderedWithIteration(IEnumerable<TOutput> outputItems)
+ {
+ // If we're bounding, we need to increment the bounded count
+ // for each individual item as we enumerate it.
+ if (_target.IsBounded)
+ {
+ // When the input item that generated this
+ // output was loaded, we incremented the bounding count. If it only
+ // output a single a item, then we don't need to touch the bounding count.
+ // Otherwise, we need to adjust the bounding count accordingly.
+ bool outputFirstItem = false;
+ try
+ {
+ foreach (TOutput item in outputItems)
+ {
+ if (outputFirstItem) _target.ChangeBoundingCount(count: 1);
+ else outputFirstItem = true;
+ _source.AddMessage(item);
+ }
+ }
+ finally
+ {
+ if (!outputFirstItem) _target.ChangeBoundingCount(count: -1);
+ }
+ }
+ // If we're not bounding, just output each individual item.
+ else
+ {
+ foreach (TOutput item in outputItems) _source.AddMessage(item);
+ }
+ }
+
+ /// <summary>
+ /// Updates the bounding count based on the number of output items
+ /// generated for a single input.
+ /// </summary>
+ /// <param name="count">The number of output items.</param>
+ private void UpdateBoundingCountWithOutputCount(int count)
+ {
+ // We already incremented the count for a single input item, and
+ // that input spawned 0 or more outputs. Take the input tracking
+ // into account when figuring out how much to increment or decrement
+ // the bounding count.
+
+ Contract.Requires(_target.IsBounded, "Expected to be in bounding mode.");
+ if (count > 1) _target.ChangeBoundingCount(count - 1);
+ else if (count == 0) _target.ChangeBoundingCount(-1);
+ else Debug.Assert(count == 1, "Count shouldn't be negative.");
+ }
+
+ /// <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 TransformManyBlock.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The transform many block being viewed.</summary>
+ private readonly TransformManyBlock<TInput, TOutput> _transformManyBlock;
+ /// <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="transformManyBlock">The transform being viewed.</param>
+ public DebugView(TransformManyBlock<TInput, TOutput> transformManyBlock)
+ {
+ Contract.Requires(transformManyBlock != null, "Need a block with which to construct the debug view.");
+ _transformManyBlock = transformManyBlock;
+ _targetDebuggingInformation = transformManyBlock._target.GetDebuggingInformation();
+ _sourceDebuggingInformation = transformManyBlock._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 input operations currently in flight.</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(_transformManyBlock); } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<TOutput> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } }
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public ITargetBlock<TOutput> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// WriteOnceBlock.cs
+//
+//
+// A propagator block capable of receiving and storing only one message, ever.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Security;
+using System.Threading.Tasks.Dataflow.Internal;
+
+namespace System.Threading.Tasks.Dataflow
+{
+ /// <summary>Provides a buffer for receiving and storing at most one element in a network of dataflow blocks.</summary>
+ /// <typeparam name="T">Specifies the type of the data buffered by this dataflow block.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(WriteOnceBlock<>.DebugView))]
+ public sealed class WriteOnceBlock<T> : IPropagatorBlock<T, T>, IReceivableSourceBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>A registry used to store all linked targets and information about them.</summary>
+ private readonly TargetRegistry<T> _targetRegistry;
+ /// <summary>The cloning function.</summary>
+ private readonly Func<T, T> _cloningFunction;
+ /// <summary>The options used to configure this block's execution.</summary>
+ private readonly DataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>Lazily initialized task completion source that produces the actual completion task when needed.</summary>
+ private TaskCompletionSource<VoidResult> _lazyCompletionTaskSource;
+ /// <summary>Whether all future messages should be declined.</summary>
+ private bool _decliningPermanently;
+ /// <summary>Whether block completion is disallowed.</summary>
+ private bool _completionReserved;
+ /// <summary>The header of the singly-assigned value.</summary>
+ private DataflowMessageHeader _header;
+ /// <summary>The singly-assigned value.</summary>
+ private T _value;
+
+ /// <summary>Gets the object used as the value lock.</summary>
+ private object ValueLock { get { return _targetRegistry; } }
+
+ /// <summary>Initializes the <see cref="WriteOnceBlock{T}"/>.</summary>
+ /// <param name="cloningFunction">
+ /// The function to use to clone the data when offered to other blocks.
+ /// This may be null to indicate that no cloning need be performed.
+ /// </param>
+ public WriteOnceBlock(Func<T, T> cloningFunction) :
+ this(cloningFunction, DataflowBlockOptions.Default)
+ { }
+
+ /// <summary>Initializes the <see cref="WriteOnceBlock{T}"/> with the specified <see cref="DataflowBlockOptions"/>.</summary>
+ /// <param name="cloningFunction">
+ /// The function to use to clone the data when offered to other blocks.
+ /// This may be null to indicate that no cloning need be performed.
+ /// </param>
+ /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="WriteOnceBlock{T}"/>.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception>
+ public WriteOnceBlock(Func<T, T> cloningFunction, DataflowBlockOptions dataflowBlockOptions)
+ {
+ // Validate arguments
+ if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions");
+ Contract.EndContractBlock();
+
+ // Store the option
+ _cloningFunction = cloningFunction;
+ _dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone();
+
+ // The target registry also serves as our ValueLock,
+ // and thus must always be initialized, even if the block is pre-canceled, as
+ // subsequent usage of the block may run through code paths that try to take this lock.
+ _targetRegistry = new TargetRegistry<T>(this);
+
+ // If a cancelable CancellationToken has been passed in,
+ // we need to initialize the completion task's TCS now.
+ if (dataflowBlockOptions.CancellationToken.CanBeCanceled)
+ {
+ _lazyCompletionTaskSource = new TaskCompletionSource<VoidResult>();
+
+ // If we've already had cancellation requested, do as little work as we have to
+ // in order to be done.
+ if (dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ {
+ _completionReserved = _decliningPermanently = true;
+
+ // Cancel the completion task's TCS
+ _lazyCompletionTaskSource.SetCanceled();
+ }
+ else
+ {
+ // Handle async cancellation requests by declining on the target
+ Common.WireCancellationToComplete(
+ dataflowBlockOptions.CancellationToken, _lazyCompletionTaskSource.Task, state => ((WriteOnceBlock<T>)state).Complete(), this);
+ }
+ }
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCreated(this, dataflowBlockOptions);
+ }
+#endif
+ }
+
+ /// <summary>Asynchronously completes the block on another task.</summary>
+ /// <remarks>
+ /// This must only be called once all of the completion conditions are met.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ private void CompleteBlockAsync(IList<Exception> exceptions)
+ {
+ Contract.Requires(_decliningPermanently, "We may get here only after we have started to decline permanently.");
+ Contract.Requires(_completionReserved, "We may get here only after we have reserved completion.");
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ // If there is no exceptions list, we offer the message around, and then complete.
+ // If there is an exception list, we complete without offering the message.
+ if (exceptions == null)
+ {
+ // Offer the message to any linked targets and complete the block asynchronously to avoid blocking the caller
+ var taskForOutputProcessing = new Task(state => ((WriteOnceBlock<T>)state).OfferToTargetsAndCompleteBlock(), this,
+ Common.GetCreationOptionsForTask());
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ this, taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _header.IsValid ? 1 : 0);
+ }
+#endif
+
+ // Start the task handling scheduling exceptions
+ Exception exception = Common.StartTaskSafe(taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler);
+ if (exception != null) CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: true);
+ }
+ else
+ {
+ // Complete the block asynchronously to avoid blocking the caller
+ Task.Factory.StartNew(state =>
+ {
+ Tuple<WriteOnceBlock<T>, IList<Exception>> blockAndList = (Tuple<WriteOnceBlock<T>, IList<Exception>>)state;
+ blockAndList.Item1.CompleteBlock(blockAndList.Item2);
+ },
+ Tuple.Create(this, exceptions), CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>Offers the message and completes the block.</summary>
+ /// <remarks>
+ /// This is called only once.
+ /// </remarks>
+ private void OfferToTargetsAndCompleteBlock()
+ {
+ // OfferToTargets calls to potentially multiple targets, each of which
+ // could be faulty and throw an exception. OfferToTargets creates a
+ // list of all such exceptions and returns it.
+ // If _value is null, OfferToTargets does nothing.
+ List<Exception> exceptions = OfferToTargets();
+ CompleteBlock(exceptions);
+ }
+
+ /// <summary>Completes the block.</summary>
+ /// <remarks>
+ /// This is called only once.
+ /// </remarks>
+ private void CompleteBlock(IList<Exception> exceptions)
+ {
+ // Do not invoke the CompletionTaskSource property if there is a chance that _lazyCompletionTaskSource
+ // has not been initialized yet and we may have to complete normally, because that would defeat the
+ // sole purpose of the TCS being lazily initialized.
+
+ Contract.Requires(_lazyCompletionTaskSource == null || !_lazyCompletionTaskSource.Task.IsCompleted, "The task completion source must not be completed. This must be the only thread that ever completes the block.");
+
+ // Save the linked list of targets so that it could be traversed later to propagate completion
+ TargetRegistry<T>.LinkedTargetInfo linkedTargets = _targetRegistry.ClearEntryPoints();
+
+ // Complete the block's completion task
+ if (exceptions != null && exceptions.Count > 0)
+ {
+ CompletionTaskSource.TrySetException(exceptions);
+ }
+ else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ {
+ CompletionTaskSource.TrySetCanceled();
+ }
+ else
+ {
+ // Safely try to initialize the completion task's TCS with a cached completed TCS.
+ // If our attempt succeeds (CompareExchange returns null), we have nothing more to do.
+ // If the completion task's TCS was already initialized (CompareExchange returns non-null),
+ // we have to complete that TCS instance.
+ if (Interlocked.CompareExchange(ref _lazyCompletionTaskSource, Common.CompletedVoidResultTaskCompletionSource, null) != null)
+ {
+ _lazyCompletionTaskSource.TrySetResult(default(VoidResult));
+ }
+ }
+
+ // Now that the completion task is completed, we may propagate completion to the linked targets
+ _targetRegistry.PropagateCompletion(linkedTargets);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCompleted(this);
+ }
+#endif
+ }
+
+ /// <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);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ public void Complete()
+ {
+ CompleteCore(exception: null, storeExceptionEvenIfAlreadyCompleting: false);
+ }
+
+ private void CompleteCore(Exception exception, bool storeExceptionEvenIfAlreadyCompleting)
+ {
+ Contract.Requires(exception != null || !storeExceptionEvenIfAlreadyCompleting,
+ "When storeExceptionEvenIfAlreadyCompleting is set to true, an exception must be provided.");
+ Contract.EndContractBlock();
+
+ bool thisThreadReservedCompletion = false;
+ lock (ValueLock)
+ {
+ // Faulting from outside is allowed until we start declining permanently
+ if (_decliningPermanently && !storeExceptionEvenIfAlreadyCompleting) return;
+
+ // Decline further messages
+ _decliningPermanently = true;
+
+ // Reserve Completion.
+ // If storeExceptionEvenIfAlreadyCompleting is true, we are here to fault the block,
+ // because we couldn't launch the offer-and-complete task.
+ // We have to retry to just complete. We do that by pretending completion wasn't reserved.
+ if (!_completionReserved || storeExceptionEvenIfAlreadyCompleting) thisThreadReservedCompletion = _completionReserved = true;
+ }
+
+ // This call caused us to start declining further messages,
+ // there's nothing more this block needs to do... complete it if we just reserved completion.
+ if (thisThreadReservedCompletion)
+ {
+ List<Exception> exceptions = null;
+ if (exception != null)
+ {
+ exceptions = new List<Exception>();
+ exceptions.Add(exception);
+ }
+
+ CompleteBlockAsync(exceptions);
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ public Boolean TryReceive(Predicate<T> filter, out T item)
+ {
+ // No need to take the outgoing lock, as we don't need to synchronize with other
+ // targets, and _value only ever goes from null to non-null, not the other way around.
+
+ // If we have a value, give it up. All receives on a successfully
+ // completed WriteOnceBlock will return true, as long as the message
+ // passes the filter (all messages pass a null filter).
+ if (_header.IsValid && (filter == null || filter(_value)))
+ {
+ item = CloneItem(_value);
+ return true;
+ }
+ // Otherwise, nothing to receive
+ else
+ {
+ item = default(T);
+ return false;
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ Boolean IReceivableSourceBlock<T>.TryReceiveAll(out IList<T> items)
+ {
+ // Try to receive the one item this block may have.
+ // If we can, give back an array of one item. Otherwise,
+ // give back null.
+ T item;
+ if (TryReceive(null, out item))
+ {
+ items = new T[] { item };
+ return true;
+ }
+ else
+ {
+ items = null;
+ return false;
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ public IDisposable LinkTo(ITargetBlock<T> target, DataflowLinkOptions linkOptions)
+ {
+ // Validate arguments
+ if (target == null) throw new ArgumentNullException("target");
+ if (linkOptions == null) throw new ArgumentNullException("linkOptions");
+ Contract.EndContractBlock();
+
+ bool hasValue;
+ bool isCompleted;
+ lock (ValueLock)
+ {
+ hasValue = HasValue;
+ isCompleted = _completionReserved;
+
+ // If we haven't gotten a value yet and the block is not complete, add the target and bail
+ if (!hasValue && !isCompleted)
+ {
+ _targetRegistry.Add(ref target, linkOptions);
+ return Common.CreateUnlinker(ValueLock, _targetRegistry, target);
+ }
+ }
+
+ // If we already have a value, send it along to the linking target
+ if (hasValue)
+ {
+ bool useCloning = _cloningFunction != null;
+ target.OfferMessage(_header, _value, this, consumeToAccept: useCloning);
+ }
+
+ // If completion propagation has been requested, do it safely.
+ // The Completion property will ensure the lazy TCS is initialized.
+ if (linkOptions.PropagateCompletion) Common.PropagateCompletionOnceCompleted(Completion, target);
+
+ return Disposables.Nop;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ public Task Completion { get { return CompletionTaskSource.Task; } }
+
+ /// <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();
+
+ bool thisThreadReservedCompletion = false;
+ lock (ValueLock)
+ {
+ // If we are declining messages, bail
+ if (_decliningPermanently) return DataflowMessageStatus.DecliningPermanently;
+
+ // Consume the message from the source if necessary. We do this while holding ValueLock to prevent multiple concurrent
+ // offers from all succeeding.
+ if (consumeToAccept)
+ {
+ bool consumed;
+ messageValue = source.ConsumeMessage(messageHeader, this, out consumed);
+ if (!consumed) return DataflowMessageStatus.NotAvailable;
+ }
+
+ // Update the header and the value
+ _header = Common.SingleMessageHeader;
+ _value = messageValue;
+
+ // We got what we needed. Start declining permanently.
+ _decliningPermanently = true;
+
+ // Reserve Completion
+ if (!_completionReserved) thisThreadReservedCompletion = _completionReserved = true;
+ }
+
+ // Since this call to OfferMessage succeeded (and only one can ever), complete the block
+ // (but asynchronously so as not to block the Post call while offering to
+ // targets, running synchronous continuations off of the completion task, etc.)
+ if (thisThreadReservedCompletion) CompleteBlockAsync(exceptions: null);
+ return DataflowMessageStatus.Accepted;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ T ISourceBlock<T>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target, out Boolean messageConsumed)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // As long as the message being requested is the one we have, allow it to be consumed,
+ // but make a copy using the provided cloning function.
+ if (_header.Id == messageHeader.Id)
+ {
+ messageConsumed = true;
+ return CloneItem(_value);
+ }
+ else
+ {
+ messageConsumed = false;
+ return default(T);
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ Boolean ISourceBlock<T>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // As long as the message is the one we have, it can be "reserved."
+ // Reservations on a WriteOnceBlock are not exclusive, because
+ // everyone who wants a copy can get one.
+ return _header.Id == messageHeader.Id;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<T>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ // As long as the message is the one we have, everything's fine.
+ if (_header.Id != messageHeader.Id) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+
+ // In other blocks, upon release we typically re-offer the message to all linked targets.
+ // We need to do the same thing for WriteOnceBlock, in order to account for cases where the block
+ // may be linked to a join or similar block, such that the join could never again be satisfied
+ // if it didn't receive another offer from this source. However, since the message is broadcast
+ // and all targets can get a copy, we don't need to broadcast to all targets, only to
+ // the target that released the message. Note that we don't care whether it's accepted
+ // or not, nor do we care about any exceptions which may emerge (they should just propagate).
+ Debug.Assert(_header.IsValid, "A valid header is required.");
+ bool useCloning = _cloningFunction != null;
+ target.OfferMessage(_header, _value, this, consumeToAccept: useCloning);
+ }
+
+ /// <summary>Clones the item.</summary>
+ /// <param name="item">The item to clone.</param>
+ /// <returns>The cloned item.</returns>
+ private T CloneItem(T item)
+ {
+ return _cloningFunction != null ?
+ _cloningFunction(item) :
+ item;
+ }
+
+ /// <summary>Offers the WriteOnceBlock's message to all targets.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private List<Exception> OfferToTargets()
+ {
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ // If there is a message, offer it to everyone. Return values
+ // don't matter, because we only get one message and then complete,
+ // and everyone who wants a copy can get a copy.
+ List<Exception> exceptions = null;
+ if (HasValue)
+ {
+ TargetRegistry<T>.LinkedTargetInfo cur = _targetRegistry.FirstTargetNode;
+ while (cur != null)
+ {
+ TargetRegistry<T>.LinkedTargetInfo next = cur.Next;
+ ITargetBlock<T> target = cur.Target;
+ try
+ {
+ // Offer the message. If there's a cloning function, we force the target to
+ // come back to us to consume the message, allowing us the opportunity to run
+ // the cloning function once we know they want the data. If there is no cloning
+ // function, there's no reason for them to call back here.
+ bool useCloning = _cloningFunction != null;
+ target.OfferMessage(_header, _value, this, consumeToAccept: useCloning);
+ }
+ catch (Exception exc)
+ {
+ // Track any erroneous exceptions that may occur
+ // and return them to the caller so that they may
+ // be logged in the completion task.
+ Common.StoreDataflowMessageValueIntoExceptionData(exc, _value);
+ Common.AddException(ref exceptions, exc);
+ }
+ cur = next;
+ }
+ }
+ return exceptions;
+ }
+
+ /// <summary>Ensures the completion task's TCS is initialized.</summary>
+ /// <returns>The completion task's TCS.</returns>
+ private TaskCompletionSource<VoidResult> CompletionTaskSource
+ {
+ get
+ {
+ // If the completion task's TCS has not been initialized by now, safely try to initialize it.
+ // It is very important that once a completion task/source instance has been handed out,
+ // it remains the block's completion task.
+ if (_lazyCompletionTaskSource == null)
+ {
+ Interlocked.CompareExchange(ref _lazyCompletionTaskSource, new TaskCompletionSource<VoidResult>(), null);
+ }
+
+ return _lazyCompletionTaskSource;
+ }
+ }
+
+ /// <summary>Gets whether the block is storing a value.</summary>
+ private bool HasValue { get { return _header.IsValid; } }
+ /// <summary>Gets the value being stored by the block.</summary>
+ private T Value { get { return _header.IsValid ? _value : default(T); } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="ToString"]/*' />
+ public override string ToString() { return Common.GetNameForDebugger(this, _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}, HasValue={1}, Value={2}",
+ Common.GetNameForDebugger(this, _dataflowBlockOptions), HasValue, Value);
+ }
+ }
+ /// <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 WriteOnceBlock.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The WriteOnceBlock being viewed.</summary>
+ private readonly WriteOnceBlock<T> _writeOnceBlock;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="writeOnceBlock">The WriteOnceBlock to view.</param>
+ public DebugView(WriteOnceBlock<T> writeOnceBlock)
+ {
+ Contract.Requires(writeOnceBlock != null, "Need a block with which to construct the debug view.");
+ _writeOnceBlock = writeOnceBlock;
+ }
+
+ /// <summary>Gets whether the WriteOnceBlock has completed.</summary>
+ public bool IsCompleted { get { return _writeOnceBlock.Completion.IsCompleted; } }
+ /// <summary>Gets the block's Id.</summary>
+ public int Id { get { return Common.GetBlockId(_writeOnceBlock); } }
+
+ /// <summary>Gets whether the WriteOnceBlock has a value.</summary>
+ public bool HasValue { get { return _writeOnceBlock.HasValue; } }
+ /// <summary>Gets the WriteOnceBlock's value if it has one, or default(T) if it doesn't.</summary>
+ public T Value { get { return _writeOnceBlock.Value; } }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ public DataflowBlockOptions DataflowBlockOptions { get { return _writeOnceBlock._dataflowBlockOptions; } }
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ public TargetRegistry<T> LinkedTargets { get { return _writeOnceBlock._targetRegistry; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ActionOnDispose.cs
+//
+//
+// Implemention of IDisposable that runs a delegate on Dispose.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Provider of disposables that run actions.</summary>
+ internal sealed class Disposables
+ {
+ /// <summary>An IDisposable that does nothing.</summary>
+ internal readonly static IDisposable Nop = new NopDisposable();
+
+ /// <summary>Creates an IDisposable that runs an action when disposed.</summary>
+ /// <typeparam name="T1">Specifies the type of the first argument.</typeparam>
+ /// <typeparam name="T2">Specifies the type of the second argument.</typeparam>
+ /// <param name="action">The action to invoke.</param>
+ /// <param name="arg1">The first argument.</param>
+ /// <param name="arg2">The second argument.</param>
+ /// <returns>The created disposable.</returns>
+ internal static IDisposable Create<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
+ {
+ Contract.Requires(action != null, "Non-null disposer action required.");
+ return new Disposable<T1, T2>(action, arg1, arg2);
+ }
+
+ /// <summary>Creates an IDisposable that runs an action when disposed.</summary>
+ /// <typeparam name="T1">Specifies the type of the first argument.</typeparam>
+ /// <typeparam name="T2">Specifies the type of the second argument.</typeparam>
+ /// <typeparam name="T3">Specifies the type of the third argument.</typeparam>
+ /// <param name="action">The action to invoke.</param>
+ /// <param name="arg1">The first argument.</param>
+ /// <param name="arg2">The second argument.</param>
+ /// <param name="arg3">The third argument.</param>
+ /// <returns>The created disposable.</returns>
+ internal static IDisposable Create<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
+ {
+ Contract.Requires(action != null, "Non-null disposer action required.");
+ return new Disposable<T1, T2, T3>(action, arg1, arg2, arg3);
+ }
+
+ /// <summary>A disposable that's a nop.</summary>
+ [DebuggerDisplay("Disposed = true")]
+ private sealed class NopDisposable : IDisposable
+ {
+ void IDisposable.Dispose() { }
+ }
+
+ /// <summary>An IDisposable that will run a delegate when disposed.</summary>
+ [DebuggerDisplay("Disposed = {Disposed}")]
+ private sealed class Disposable<T1, T2> : IDisposable
+ {
+ /// <summary>First state argument.</summary>
+ private readonly T1 _arg1;
+ /// <summary>Second state argument.</summary>
+ private readonly T2 _arg2;
+ /// <summary>The action to run when disposed. Null if disposed.</summary>
+ private Action<T1, T2> _action;
+
+ /// <summary>Initializes the ActionOnDispose.</summary>
+ /// <param name="action">The action to run when disposed.</param>
+ /// <param name="arg1">The first argument.</param>
+ /// <param name="arg2">The second argument.</param>
+ internal Disposable(Action<T1, T2> action, T1 arg1, T2 arg2)
+ {
+ Contract.Requires(action != null, "Non-null action needed for disposable");
+ _action = action;
+ _arg1 = arg1;
+ _arg2 = arg2;
+ }
+
+ /// <summary>Gets whether the IDisposable has been disposed.</summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ private bool Disposed { get { return _action == null; } }
+
+ /// <summary>Invoke the action.</summary>
+ void IDisposable.Dispose()
+ {
+ Action<T1, T2> toRun = _action;
+ if (toRun != null &&
+ Interlocked.CompareExchange(ref _action, null, toRun) == toRun)
+ {
+ toRun(_arg1, _arg2);
+ }
+ }
+ }
+
+ /// <summary>An IDisposable that will run a delegate when disposed.</summary>
+ [DebuggerDisplay("Disposed = {Disposed}")]
+ private sealed class Disposable<T1, T2, T3> : IDisposable
+ {
+ /// <summary>First state argument.</summary>
+ private readonly T1 _arg1;
+ /// <summary>Second state argument.</summary>
+ private readonly T2 _arg2;
+ /// <summary>Third state argument.</summary>
+ private readonly T3 _arg3;
+ /// <summary>The action to run when disposed. Null if disposed.</summary>
+ private Action<T1, T2, T3> _action;
+
+ /// <summary>Initializes the ActionOnDispose.</summary>
+ /// <param name="action">The action to run when disposed.</param>
+ /// <param name="arg1">The first argument.</param>
+ /// <param name="arg2">The second argument.</param>
+ /// <param name="arg3">The third argument.</param>
+ internal Disposable(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
+ {
+ Contract.Requires(action != null, "Non-null action needed for disposable");
+ _action = action;
+ _arg1 = arg1;
+ _arg2 = arg2;
+ _arg3 = arg3;
+ }
+
+ /// <summary>Gets whether the IDisposable has been disposed.</summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ private bool Disposed { get { return _action == null; } }
+
+ /// <summary>Invoke the action.</summary>
+ void IDisposable.Dispose()
+ {
+ Action<T1, T2, T3> toRun = _action;
+ if (toRun != null &&
+ Interlocked.CompareExchange(ref _action, null, toRun) == toRun)
+ {
+ toRun(_arg1, _arg2, _arg3);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// Common.cs
+//
+//
+// Helper routines for the rest of the TPL Dataflow implementation.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Security;
+using System.Collections;
+using System.Runtime.ExceptionServices;
+using System.Threading.Tasks.Dataflow.Internal.Threading;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Internal helper utilities.</summary>
+ internal static class Common
+ {
+ /// <summary>
+ /// An invalid ID to assign for reordering purposes. This value is chosen to be the last of the 64-bit integers that
+ /// could ever be assigned as a reordering ID.
+ /// </summary>
+ internal const long INVALID_REORDERING_ID = -1;
+ /// <summary>A well-known message ID for code that will send exactly one message or
+ /// where the exact message ID is not important.</summary>
+ internal const int SINGLE_MESSAGE_ID = 1;
+ /// <summary>A perf optimization for caching a well-known message header instead of
+ /// constructing one every time it is needed.</summary>
+ internal static readonly DataflowMessageHeader SingleMessageHeader = new DataflowMessageHeader(SINGLE_MESSAGE_ID);
+ /// <summary>The cached completed Task{bool} with a result of true.</summary>
+ internal static readonly Task<bool> CompletedTaskWithTrueResult = CreateCachedBooleanTask(true);
+ /// <summary>The cached completed Task{bool} with a result of false.</summary>
+ internal static readonly Task<bool> CompletedTaskWithFalseResult = CreateCachedBooleanTask(false);
+ /// <summary>The cached completed TaskCompletionSource{VoidResult}.</summary>
+ internal static readonly TaskCompletionSource<VoidResult> CompletedVoidResultTaskCompletionSource = CreateCachedTaskCompletionSource<VoidResult>();
+
+ /// <summary>Asserts that a given synchronization object is either held or not held.</summary>
+ /// <param name="syncObj">The monitor to check.</param>
+ /// <param name="held">Whether we want to assert that it's currently held or not held.</param>
+ [Conditional("DEBUG")]
+ internal static void ContractAssertMonitorStatus(object syncObj, bool held)
+ {
+ Contract.Requires(syncObj != null, "The monitor object to check must be provided.");
+ Debug.Assert(Monitor.IsEntered(syncObj) == held, "The locking scheme was not correctly followed.");
+ }
+
+ /// <summary>Keeping alive processing tasks: maximum number of processed messages.</summary>
+ internal const int KEEP_ALIVE_NUMBER_OF_MESSAGES_THRESHOLD = 1;
+ /// <summary>Keeping alive processing tasks: do not attempt this many times.</summary>
+ internal const int KEEP_ALIVE_BAN_COUNT = 1000;
+
+ /// <summary>A predicate type for TryKeepAliveUntil.</summary>
+ /// <param name="stateIn">Input state for the predicate in order to avoid closure allocations.</param>
+ /// <param name="stateOut">Output state for the predicate in order to avoid closure allocations.</param>
+ /// <returns>The state of the predicate.</returns>
+ internal delegate bool KeepAlivePredicate<TStateIn, TStateOut>(TStateIn stateIn, out TStateOut stateOut);
+
+ /// <summary>Actively waits for a predicate to become true.</summary>
+ /// <param name="predicate">The predicate to become true.</param>
+ /// <param name="stateIn">Input state for the predicate in order to avoid closure allocations.</param>
+ /// <param name="stateOut">Output state for the predicate in order to avoid closure allocations.</param>
+ /// <returns>True if the predicate was evaluated and it returned true. False otherwise.</returns>
+ internal static bool TryKeepAliveUntil<TStateIn, TStateOut>(KeepAlivePredicate<TStateIn, TStateOut> predicate,
+ TStateIn stateIn, out TStateOut stateOut)
+ {
+ Contract.Requires(predicate != null, "Non-null predicate to execute is required.");
+ const int ITERATION_LIMIT = 16;
+
+ for (int c = ITERATION_LIMIT; c > 0; c--)
+ {
+ if (!Thread.Yield())
+ {
+ // There was no other thread waiting.
+ // We may spend some more cycles to evaluate the predicate.
+ if (predicate(stateIn, out stateOut)) return true;
+ }
+ }
+
+ stateOut = default(TStateOut);
+ return false;
+ }
+
+ /// <summary>Unwraps an instance T from object state that is a WeakReference to that instance.</summary>
+ /// <typeparam name="T">The type of the data to be unwrapped.</typeparam>
+ /// <param name="state">The weak reference.</param>
+ /// <returns>The T instance.</returns>
+ internal static T UnwrapWeakReference<T>(object state) where T : class
+ {
+ var wr = state as WeakReference<T>;
+ Debug.Assert(wr != null, "Expected a WeakReference<T> as the state argument");
+ T item;
+ return wr.TryGetTarget(out item) ? item : null;
+ }
+
+ /// <summary>Gets an ID for the dataflow block.</summary>
+ /// <param name="block">The dataflow block.</param>
+ /// <returns>An ID for the dataflow block.</returns>
+ internal static int GetBlockId(IDataflowBlock block)
+ {
+ Contract.Requires(block != null, "Block required to extract an Id.");
+ const int NOTASKID = 0; // tasks don't have 0 as ids
+ Task t = Common.GetPotentiallyNotSupportedCompletionTask(block);
+ return t != null ? t.Id : NOTASKID;
+ }
+
+ /// <summary>Gets the name for the specified block, suitable to be rendered in a debugger window.</summary>
+ /// <param name="block">The block for which a name is needed.</param>
+ /// <param name="options">
+ /// The options to use when rendering the name. If no options are provided, the block's name is used directly.
+ /// </param>
+ /// <returns>The name of the object.</returns>
+ /// <remarks>This is used from DebuggerDisplay attributes.</remarks>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ internal static string GetNameForDebugger(
+ IDataflowBlock block, DataflowBlockOptions options = null)
+ {
+ Contract.Requires(block != null, "Should only be used with valid objects being displayed in the debugger.");
+ Contract.Requires(options == null || options.NameFormat != null, "If options are provided, NameFormat must be valid.");
+
+ if (block == null) return string.Empty;
+
+ string blockName = block.GetType().Name;
+ if (options == null) return blockName;
+
+ // {0} == block name
+ // {1} == block id
+ int blockId = GetBlockId(block);
+
+ // Since NameFormat is public, formatting may throw if the user has set
+ // a string that contains a reference to an argument higher than {1}.
+ // In the case of an exception, show the exception message.
+ try
+ {
+ return string.Format(options.NameFormat, blockName, blockId);
+ }
+ catch (Exception exception)
+ {
+ return exception.Message;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the exception represents a cooperative cancellation acknowledgment.
+ /// </summary>
+ /// <param name="exception">The exception to check.</param>
+ /// <returns>true if this exception represents a cooperative cancellation acknowledgment; otherwise, false.</returns>
+ internal static bool IsCooperativeCancellation(Exception exception)
+ {
+ Contract.Requires(exception != null, "An exception to check for cancellation must be provided.");
+ return exception is OperationCanceledException;
+ // Note that the behavior of this method does not exactly match that of Parallel.*, PLINQ, and Task.Factory.StartNew,
+ // in that it's more liberal and treats any OCE as acknowledgment of cancellation; in contrast, the other
+ // libraries only treat OCEs as such if they contain the same token that was provided by the user
+ // and if that token has cancellation requested. Such logic could be achieved here with:
+ // var oce = exception as OperationCanceledException;
+ // return oce != null &&
+ // oce.CancellationToken == dataflowBlockOptions.CancellationToken &&
+ // oce.CancellationToken.IsCancellationRequested;
+ // However, that leads to a discrepancy with the async processing case of dataflow blocks,
+ // where tasks are returned to represent the message processing, potentially in the Canceled state,
+ // and we simply ignore such tasks. Further, for blocks like TransformBlock, it's useful to be able
+ // to cancel an individual operation which must return a TOutput value, simply by throwing an OperationCanceledException.
+ // In such cases, you wouldn't want cancellation tied to the token, because you would only be able to
+ // cancel an individual message processing if the whole block was canceled.
+ }
+
+ /// <summary>Registers a block for cancellation by completing when cancellation is requested.</summary>
+ /// <param name="cancellationToken">The block's cancellation token.</param>
+ /// <param name="completionTask">The task that will complete when the block is completely done processing.</param>
+ /// <param name="completeAction">An action that will decline permanently on the state passed to it.</param>
+ /// <param name="completeState">The block on which to decline permanently.</param>
+ internal static void WireCancellationToComplete(
+ CancellationToken cancellationToken, Task completionTask, Action<object> completeAction, object completeState)
+ {
+ Contract.Requires(completionTask != null, "A task to wire up for completion is needed.");
+ Contract.Requires(completeAction != null, "An action to invoke upon cancellation is required.");
+
+ // If a cancellation request has already occurred, just invoke the declining action synchronously.
+ // CancellationToken would do this anyway but we can short-circuit it further and avoid a bunch of unnecessary checks.
+ if (cancellationToken.IsCancellationRequested)
+ {
+ completeAction(completeState);
+ }
+ // Otherwise, if a cancellation request occurs, we want to prevent the block from accepting additional
+ // data, and we also want to dispose of that registration when we complete so that we don't
+ // leak into a long-living cancellation token.
+ else if (cancellationToken.CanBeCanceled)
+ {
+ CancellationTokenRegistration reg = cancellationToken.Register(completeAction, completeState);
+ completionTask.ContinueWith((completed, state) => ((CancellationTokenRegistration)state).Dispose(),
+ reg, cancellationToken, Common.GetContinuationOptions(), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>Initializes the stack trace and watson bucket of an inactive exception.</summary>
+ /// <param name="exception">The exception to initialize.</param>
+ /// <returns>The initialized exception.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal static Exception InitializeStackTrace(Exception exception)
+ {
+ Contract.Requires(exception != null && exception.StackTrace == null,
+ "A valid but uninitialized exception should be provided.");
+ try { throw exception; }
+ catch { return exception; }
+ }
+
+ /// <summary>The name of the key in an Exception's Data collection used to store information on a dataflow message.</summary>
+ internal const string EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE = "DataflowMessageValue"; // should not be localized
+
+ /// <summary>Stores details on a dataflow message into an Exception's Data collection.</summary>
+ /// <typeparam name="T">Specifies the type of data stored in the message.</typeparam>
+ /// <param name="exc">The Exception whose Data collection should store message information.</param>
+ /// <param name="messageValue">The message information to be stored.</param>
+ /// <param name="targetInnerExceptions">Whether to store the data into the exception's inner exception(s) in addition to the exception itself.</param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal static void StoreDataflowMessageValueIntoExceptionData<T>(Exception exc, T messageValue, bool targetInnerExceptions = false)
+ {
+ Contract.Requires(exc != null, "The exception into which data should be stored must be provided.");
+
+ // Get the string value to store
+ string strValue = messageValue as string;
+ if (strValue == null && messageValue != null)
+ {
+ try
+ {
+ strValue = messageValue.ToString();
+ }
+ catch { /* It's ok to eat all exceptions here. If ToString throws, we'll just ignore it. */ }
+ }
+ if (strValue == null) return;
+
+ // Store the data into the exception itself
+ StoreStringIntoExceptionData(exc, Common.EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE, strValue);
+
+ // If we also want to target inner exceptions...
+ if (targetInnerExceptions)
+ {
+ // If this is an aggregate, store into all inner exceptions.
+ var aggregate = exc as AggregateException;
+ if (aggregate != null)
+ {
+ foreach (Exception innerException in aggregate.InnerExceptions)
+ {
+ StoreStringIntoExceptionData(innerException, Common.EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE, strValue);
+ }
+ }
+ // Otherwise, if there's an Exception.InnerException, store into that.
+ else if (exc.InnerException != null)
+ {
+ StoreStringIntoExceptionData(exc.InnerException, Common.EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE, strValue);
+ }
+ }
+ }
+
+ /// <summary>Stores the specified string value into the specified key slot of the specified exception's data dictionary.</summary>
+ /// <param name="exception">The exception into which the key/value should be stored.</param>
+ /// <param name="key">The key.</param>
+ /// <param name="value">The value to be serialized as a string and stored.</param>
+ /// <remarks>If the key is already present in the exception's data dictionary, the value is not overwritten.</remarks>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static void StoreStringIntoExceptionData(Exception exception, string key, string value)
+ {
+ Contract.Requires(exception != null, "An exception is needed to store the data into.");
+ Contract.Requires(key != null, "A key into the exception's data collection is needed.");
+ Contract.Requires(value != null, "The value to store must be provided.");
+ try
+ {
+ IDictionary data = exception.Data;
+ if (data != null && !data.IsFixedSize && !data.IsReadOnly && data[key] == null)
+ {
+ data[key] = value;
+ }
+ }
+ catch
+ {
+ // It's ok to eat all exceptions here. This could throw if an Exception type
+ // has overridden Data to behave differently than we expect.
+ }
+ }
+
+ /// <summary>Throws an exception asynchronously on the thread pool.</summary>
+ /// <param name="error">The exception to throw.</param>
+ /// <remarks>
+ /// This function is used when an exception needs to be propagated from a thread
+ /// other than the current context. This could happen, for example, if the exception
+ /// should cause the standard CLR exception escalation behavior, but we're inside
+ /// of a task that will squirrel the exception away.
+ /// </remarks>
+ internal static void ThrowAsync(Exception error)
+ {
+ ExceptionDispatchInfo edi = ExceptionDispatchInfo.Capture(error);
+ ThreadPool.QueueUserWorkItem(state => { ((ExceptionDispatchInfo)state).Throw(); }, edi);
+ }
+
+ /// <summary>Adds the exception to the list, first initializing the list if the list is null.</summary>
+ /// <param name="list">The list to add the exception to, and initialize if null.</param>
+ /// <param name="exception">The exception to add or whose inner exception(s) should be added.</param>
+ /// <param name="unwrapInnerExceptions">Unwrap and add the inner exception(s) rather than the specified exception directly.</param>
+ /// <remarks>This method is not thread-safe, in that it manipulates <paramref name="list"/> without any synchronization.</remarks>
+ internal static void AddException(ref List<Exception> list, Exception exception, bool unwrapInnerExceptions = false)
+ {
+ Contract.Requires(exception != null, "An exception to add is required.");
+ Contract.Requires(!unwrapInnerExceptions || exception.InnerException != null,
+ "If unwrapping is requested, an inner exception is required.");
+
+ // Make sure the list of exceptions is initialized (lazily).
+ if (list == null) list = new List<Exception>();
+
+ if (unwrapInnerExceptions)
+ {
+ AggregateException aggregate = exception as AggregateException;
+ if (aggregate != null)
+ {
+ list.AddRange(aggregate.InnerExceptions);
+ }
+ else
+ {
+ list.Add(exception.InnerException);
+ }
+ }
+ else list.Add(exception);
+ }
+
+ /// <summary>Creates a task we can cache for the desired Boolean result.</summary>
+ /// <param name="value">The value of the Boolean.</param>
+ /// <returns>A task that may be cached.</returns>
+ private static Task<Boolean> CreateCachedBooleanTask(bool value)
+ {
+ // AsyncTaskMethodBuilder<Boolean> caches tasks that are non-disposable.
+ // By using these same tasks, we're a bit more robust against disposals,
+ // in that such a disposed task's ((IAsyncResult)task).AsyncWaitHandle
+ // is still valid.
+ var atmb = System.Runtime.CompilerServices.AsyncTaskMethodBuilder<Boolean>.Create();
+ atmb.SetResult(value);
+ return atmb.Task; // must be accessed after SetResult to get the cached task
+ }
+
+ /// <summary>Creates a TaskCompletionSource{T} completed with a value of default(T) that we can cache.</summary>
+ /// <returns>Completed TaskCompletionSource{T} that may be cached.</returns>
+ private static TaskCompletionSource<T> CreateCachedTaskCompletionSource<T>()
+ {
+ var tcs = new TaskCompletionSource<T>();
+ tcs.SetResult(default(T));
+ return tcs;
+ }
+
+ /// <summary>Creates a task faulted with the specified exception.</summary>
+ /// <typeparam name="TResult">Specifies the type of the result for this task.</typeparam>
+ /// <param name="exception">The exception with which to complete the task.</param>
+ /// <returns>The faulted task.</returns>
+ internal static Task<TResult> CreateTaskFromException<TResult>(Exception exception)
+ {
+ var atmb = System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>.Create();
+ atmb.SetException(exception);
+ return atmb.Task;
+ }
+
+ /// <summary>Creates a task canceled with the specified cancellation token.</summary>
+ /// <typeparam name="TResult">Specifies the type of the result for this task.</typeparam>
+ /// <returns>The canceled task.</returns>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ internal static Task<TResult> CreateTaskFromCancellation<TResult>(CancellationToken cancellationToken)
+ {
+ Contract.Requires(cancellationToken.IsCancellationRequested,
+ "The task will only be immediately canceled if the token has cancellation requested already.");
+ var t = new Task<TResult>(CachedGenericDelegates<TResult>.DefaultTResultFunc, cancellationToken);
+ Debug.Assert(t.IsCanceled, "Task's constructor should cancel the task synchronously in the ctor.");
+ return t;
+ }
+
+ /// <summary>Gets the completion task of a block, and protects against common cases of the completion task not being implemented or supported.</summary>
+ /// <param name="block">The block.</param>
+ /// <returns>The completion task, or null if the block's completion task is not implemented or supported.</returns>
+ internal static Task GetPotentiallyNotSupportedCompletionTask(IDataflowBlock block)
+ {
+ Contract.Requires(block != null, "We need a block from which to retrieve a cancellation task.");
+ try
+ {
+ return block.Completion;
+ }
+ catch (NotImplementedException) { }
+ catch (NotSupportedException) { }
+ return null;
+ }
+
+ /// <summary>
+ /// Creates an IDisposable that, when disposed, will acquire the outgoing lock while removing
+ /// the target block from the target registry.
+ /// </summary>
+ /// <typeparam name="TOutput">Specifies the type of data in the block.</typeparam>
+ /// <param name="outgoingLock">The outgoing lock used to protect the target registry.</param>
+ /// <param name="targetRegistry">The target registry from which the target should be removed.</param>
+ /// <param name="targetBlock">The target to remove from the registry.</param>
+ /// <returns>An IDisposable that will unregister the target block from the registry while holding the outgoing lock.</returns>
+ internal static IDisposable CreateUnlinker<TOutput>(object outgoingLock, TargetRegistry<TOutput> targetRegistry, ITargetBlock<TOutput> targetBlock)
+ {
+ Contract.Requires(outgoingLock != null, "Monitor object needed to protect the operation.");
+ Contract.Requires(targetRegistry != null, "Registry from which to remove is required.");
+ Contract.Requires(targetBlock != null, "Target block to unlink is required.");
+ return Disposables.Create(CachedGenericDelegates<TOutput>.CreateUnlinkerShimAction,
+ outgoingLock, targetRegistry, targetBlock);
+ }
+
+ /// <summary>An infinite TimeSpan.</summary>
+ internal static readonly TimeSpan InfiniteTimeSpan = Timeout.InfiniteTimeSpan;
+
+ /// <summary>Validates that a timeout either is -1 or is non-negative and within the range of an Int32.</summary>
+ /// <param name="timeout">The timeout to validate.</param>
+ /// <returns>true if the timeout is valid; otherwise, false.</returns>
+ internal static bool IsValidTimeout(TimeSpan timeout)
+ {
+ long millisecondsTimeout = (long)timeout.TotalMilliseconds;
+ return millisecondsTimeout >= Timeout.Infinite && millisecondsTimeout <= Int32.MaxValue;
+ }
+
+ /// <summary>Gets the options to use for continuation tasks.</summary>
+ /// <param name="toInclude">Any options to include in the result.</param>
+ /// <returns>The options to use.</returns>
+ internal static TaskContinuationOptions GetContinuationOptions(TaskContinuationOptions toInclude = TaskContinuationOptions.None)
+ {
+ return toInclude | TaskContinuationOptions.DenyChildAttach;
+ }
+
+ /// <summary>Gets the options to use for tasks.</summary>
+ /// <param name="isReplacementReplica">If this task is being created to replace another.</param>
+ /// <remarks>
+ /// These options should be used for all tasks that have the potential to run user code or
+ /// that are repeatedly spawned and thus need a modicum of fair treatment.
+ /// </remarks>
+ /// <returns>The options to use.</returns>
+ internal static TaskCreationOptions GetCreationOptionsForTask(bool isReplacementReplica = false)
+ {
+ TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
+ if (isReplacementReplica) options |= TaskCreationOptions.PreferFairness;
+ return options;
+ }
+
+ /// <summary>Starts an already constructed task with handling and observing exceptions that may come from the scheduling process.</summary>
+ /// <param name="task">Task to be started.</param>
+ /// <param name="scheduler">TaskScheduler to schedule the task on.</param>
+ /// <returns>null on success, an exception reference on scheduling error. In the latter case, the task reference is nulled out.</returns>
+ internal static Exception StartTaskSafe(Task task, TaskScheduler scheduler)
+ {
+ Contract.Requires(task != null, "Task to start is required.");
+ Contract.Requires(scheduler != null, "Scheduler on which to start the task is required.");
+
+ if (scheduler == TaskScheduler.Default)
+ {
+ task.Start(scheduler);
+ return null; // We don't need to worry about scheduler exceptions from the default scheduler.
+ }
+ // Slow path with try/catch separated out so that StartTaskSafe may be inlined in the common case.
+ else return StartTaskSafeCore(task, scheduler);
+ }
+
+ /// <summary>Starts an already constructed task with handling and observing exceptions that may come from the scheduling process.</summary>
+ /// <param name="task">Task to be started.</param>
+ /// <param name="scheduler">TaskScheduler to schedule the task on.</param>
+ /// <returns>null on success, an exception reference on scheduling error. In the latter case, the task reference is nulled out.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")]
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static Exception StartTaskSafeCore(Task task, TaskScheduler scheduler)
+ {
+ Contract.Requires(task != null, "Task to start is needed.");
+ Contract.Requires(scheduler != null, "Scheduler on which to start the task is required.");
+
+ Exception schedulingException = null;
+
+ try
+ {
+ task.Start(scheduler);
+ }
+ catch (Exception caughtException)
+ {
+ // Verify TPL has faulted the task
+ Debug.Assert(task.IsFaulted, "The task should have been faulted if it failed to start.");
+
+ // Observe the task's exception
+ AggregateException ignoredTaskException = task.Exception;
+
+ schedulingException = caughtException;
+ }
+
+ return schedulingException;
+ }
+
+ /// <summary>Pops and explicitly releases postponed messages after the block is done with processing.</summary>
+ /// <remarks>No locks should be held at this time. Unfortunately we cannot assert that.</remarks>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal static void ReleaseAllPostponedMessages<T>(ITargetBlock<T> target,
+ QueuedMap<ISourceBlock<T>, DataflowMessageHeader> postponedMessages,
+ ref List<Exception> exceptions)
+ {
+ Contract.Requires(target != null, "There must be a subject target.");
+ Contract.Requires(postponedMessages != null, "The stacked map of postponed messages must exist.");
+
+ // Note that we don't synchronize on lockObject for postponedMessages here,
+ // because no one should be adding to it at this time. We do a bit of
+ // checking just for sanity's sake.
+ int initialCount = postponedMessages.Count;
+ int processedCount = 0;
+
+ KeyValuePair<ISourceBlock<T>, DataflowMessageHeader> sourceAndMessage;
+ while (postponedMessages.TryPop(out sourceAndMessage))
+ {
+ // Loop through all postponed messages declining each messages.
+ // The only way we have to do this is by reserving and then immediately releasing each message.
+ // This is important for sources like SendAsyncSource, which keep state around until
+ // they get a response to a postponed message.
+ try
+ {
+ Debug.Assert(sourceAndMessage.Key != null, "Postponed messages must have an associated source.");
+ if (sourceAndMessage.Key.ReserveMessage(sourceAndMessage.Value, target))
+ {
+ sourceAndMessage.Key.ReleaseReservation(sourceAndMessage.Value, target);
+ }
+ }
+ catch (Exception exc)
+ {
+ Common.AddException(ref exceptions, exc);
+ }
+
+ processedCount++;
+ }
+
+ Debug.Assert(processedCount == initialCount,
+ "We should have processed the exact number of elements that were initially there.");
+ }
+
+ /// <summary>Cache ThrowAsync to avoid allocations when it is passed into PropagateCompletionXxx.</summary>
+ internal static readonly Action<Exception> AsyncExceptionHandler = ThrowAsync;
+
+ /// <summary>
+ /// Propagates completion of sourceCompletionTask to target synchronously.
+ /// </summary>
+ /// <param name="sourceCompletionTask">The task whose completion is to be propagated. It must be completed.</param>
+ /// <param name="target">The block where completion is propagated.</param>
+ /// <param name="exceptionHandler">Handler for exceptions from the target. May be null which would propagate the exception to the caller.</param>
+ internal static void PropagateCompletion(Task sourceCompletionTask, IDataflowBlock target, Action<Exception> exceptionHandler)
+ {
+ Contract.Requires(sourceCompletionTask != null, "sourceCompletionTask may not be null.");
+ Contract.Requires(target != null, "The target where completion is to be propagated may not be null.");
+ Debug.Assert(sourceCompletionTask.IsCompleted, "sourceCompletionTask must be completed in order to propagate its completion.");
+
+ AggregateException exception = sourceCompletionTask.IsFaulted ? sourceCompletionTask.Exception : null;
+
+ try
+ {
+ if (exception != null) target.Fault(exception);
+ else target.Complete();
+ }
+ catch (Exception exc)
+ {
+ if (exceptionHandler != null) exceptionHandler(exc);
+ else throw;
+ }
+ }
+
+ /// <summary>
+ /// Creates a continuation off sourceCompletionTask to complete target. See PropagateCompletion.
+ /// </summary>
+ private static void PropagateCompletionAsContinuation(Task sourceCompletionTask, IDataflowBlock target)
+ {
+ Contract.Requires(sourceCompletionTask != null, "sourceCompletionTask may not be null.");
+ Contract.Requires(target != null, "The target where completion is to be propagated may not be null.");
+ sourceCompletionTask.ContinueWith((task, state) => Common.PropagateCompletion(task, (IDataflowBlock)state, AsyncExceptionHandler),
+ target, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default);
+ }
+
+ /// <summary>
+ /// Propagates completion of sourceCompletionTask to target based on sourceCompletionTask's current state. See PropagateCompletion.
+ /// </summary>
+ internal static void PropagateCompletionOnceCompleted(Task sourceCompletionTask, IDataflowBlock target)
+ {
+ Contract.Requires(sourceCompletionTask != null, "sourceCompletionTask may not be null.");
+ Contract.Requires(target != null, "The target where completion is to be propagated may not be null.");
+
+ // If sourceCompletionTask is completed, propagate completion synchronously.
+ // Otherwise hook up a continuation.
+ if (sourceCompletionTask.IsCompleted) PropagateCompletion(sourceCompletionTask, target, exceptionHandler: null);
+ else PropagateCompletionAsContinuation(sourceCompletionTask, target);
+ }
+
+ /// <summary>Static class used to cache generic delegates the C# compiler doesn't cache by default.</summary>
+ /// <remarks>Without this, we end up allocating the generic delegate each time the operation is used.</remarks>
+ static class CachedGenericDelegates<T>
+ {
+ /// <summary>A function that returns the default value of T.</summary>
+ internal readonly static Func<T> DefaultTResultFunc = () => default(T);
+ /// <summary>
+ /// A function to use as the body of ActionOnDispose in CreateUnlinkerShim.
+ /// Passed a tuple of the sync obj, the target registry, and the target block as the state parameter.
+ /// </summary>
+ internal readonly static Action<object, TargetRegistry<T>, ITargetBlock<T>> CreateUnlinkerShimAction =
+ (syncObj, registry, target) =>
+ {
+ lock (syncObj) registry.Remove(target);
+ };
+ }
+ }
+
+ /// <summary>State used only when bounding.</summary>
+ [DebuggerDisplay("BoundedCapacity={BoundedCapacity}}")]
+ internal class BoundingState
+ {
+ /// <summary>The maximum number of messages allowed to be buffered.</summary>
+ internal readonly int BoundedCapacity;
+ /// <summary>The number of messages currently stored.</summary>
+ /// <remarks>
+ /// This value may temporarily be higher than the actual number stored.
+ /// That's ok, we just can't accept any new messages if CurrentCount >= BoundedCapacity.
+ /// Worst case is that we may temporarily have fewer items in the block than our maximum allows,
+ /// but we'll never have more.
+ /// </remarks>
+ internal int CurrentCount;
+
+ /// <summary>Initializes the BoundingState.</summary>
+ /// <param name="boundedCapacity">The positive bounded capacity.</param>
+ internal BoundingState(int boundedCapacity)
+ {
+ Contract.Requires(boundedCapacity > 0, "Bounded is only supported with positive values.");
+ BoundedCapacity = boundedCapacity;
+ }
+
+ /// <summary>Gets whether there's room available to add another message.</summary>
+ internal bool CountIsLessThanBound { get { return CurrentCount < BoundedCapacity; } }
+ }
+
+ /// <summary>Stated used only when bounding and when postponed messages are stored.</summary>
+ /// <typeparam name="TInput">Specifies the type of input messages.</typeparam>
+ [DebuggerDisplay("BoundedCapacity={BoundedCapacity}, PostponedMessages={PostponedMessagesCountForDebugger}")]
+ internal class BoundingStateWithPostponed<TInput> : BoundingState
+ {
+ /// <summary>Queue of postponed messages.</summary>
+ internal readonly QueuedMap<ISourceBlock<TInput>, DataflowMessageHeader> PostponedMessages =
+ new QueuedMap<ISourceBlock<TInput>, DataflowMessageHeader>();
+ /// <summary>
+ /// The number of transfers from the postponement queue to the input queue currently being processed.
+ /// </summary>
+ /// <remarks>
+ /// Blocks that use TargetCore need to transfer messages from the postponed queue to the input messages
+ /// queue. While doing that, new incoming messages may arrive, and if they view the postponed queue
+ /// as being empty (after the block has removed the last postponed message and is consuming it, before
+ /// storing it into the input queue), they might go directly into the input queue... that will then mess
+ /// up the ordering between those postponed messages and the newly incoming messages. To address that,
+ /// OutstandingTransfers is used to track the number of transfers currently in progress. Incoming
+ /// messages must be postponed not only if there are already any postponed messages, but also if
+ /// there are any transfers in progress (i.e. this value is > 0). It's an integer because the DOP could
+ /// be greater than 1, and thus we need to ref count multiple transfers that might be in progress.
+ /// </remarks>
+ internal int OutstandingTransfers;
+
+ /// <summary>Initializes the BoundingState.</summary>
+ /// <param name="boundedCapacity">The positive bounded capacity.</param>
+ internal BoundingStateWithPostponed(int boundedCapacity) : base(boundedCapacity)
+ {
+ }
+
+ /// <summary>Gets the number of postponed messages for the debugger.</summary>
+ private int PostponedMessagesCountForDebugger { get { return PostponedMessages.Count; } }
+ }
+
+ /// <summary>Stated used only when bounding and when postponed messages and a task are stored.</summary>
+ /// <typeparam name="TInput">Specifies the type of input messages.</typeparam>
+ internal class BoundingStateWithPostponedAndTask<TInput> : BoundingStateWithPostponed<TInput>
+ {
+ /// <summary>The task used to process messages.</summary>
+ internal Task TaskForInputProcessing;
+
+ /// <summary>Initializes the BoundingState.</summary>
+ /// <param name="boundedCapacity">The positive bounded capacity.</param>
+ internal BoundingStateWithPostponedAndTask(int boundedCapacity) : base(boundedCapacity)
+ {
+ }
+ }
+
+ /// <summary>
+ /// Type used with TaskCompletionSource(Of TResult) as the TResult
+ /// to ensure that the resulting task can't be upcast to something
+ /// that in the future could lead to compat problems.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
+ [DebuggerNonUserCode]
+ internal struct VoidResult { }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#pragma warning disable 0420
+
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ConcurrentQueue.cs
+//
+//
+// A lock-free, concurrent queue primitive, and its associated debugger view type.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Security;
+using System.Threading;
+
+namespace System.Threading.Tasks.Dataflow.Internal.Collections
+{
+ /// <summary>
+ /// Represents a thread-safe first-in, first-out collection of objects.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of elements in the queue.</typeparam>
+ /// <remarks>
+ /// All public and protected members of <see cref="ConcurrentQueue{T}"/> are thread-safe and may be used
+ /// concurrently from multiple threads.
+ /// </remarks>
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<>))]
+ internal class ConcurrentQueue<T> : IProducerConsumerCollection<T>
+ {
+ //fields of ConcurrentQueue
+ private volatile Segment _head;
+
+ private volatile Segment _tail;
+
+ private T[] _serializationArray; // Used for custom serialization.
+
+ private const int SEGMENT_SIZE = 32;
+
+ //number of snapshot takers, GetEnumerator(), ToList() and ToArray() operations take snapshot.
+ internal volatile int _numSnapshotTakers = 0;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConcurrentQueue{T}"/> class.
+ /// </summary>
+ public ConcurrentQueue()
+ {
+ _head = _tail = new Segment(0, this);
+ }
+
+ /// <summary>
+ /// Initializes the contents of the queue from an existing collection.
+ /// </summary>
+ /// <param name="collection">A collection from which to copy elements.</param>
+ private void InitializeFromCollection(IEnumerable<T> collection)
+ {
+ Segment localTail = new Segment(0, this);//use this local variable to avoid the extra volatile read/write. this is safe because it is only called from ctor
+ _head = localTail;
+
+ int index = 0;
+ foreach (T element in collection)
+ {
+ Debug.Assert(index >= 0 && index < SEGMENT_SIZE);
+ localTail.UnsafeAdd(element);
+ index++;
+
+ if (index >= SEGMENT_SIZE)
+ {
+ localTail = localTail.UnsafeGrow();
+ index = 0;
+ }
+ }
+
+ _tail = localTail;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConcurrentQueue{T}"/>
+ /// class that contains elements copied from the specified collection
+ /// </summary>
+ /// <param name="collection">The collection whose elements are copied to the new <see
+ /// cref="ConcurrentQueue{T}"/>.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="collection"/> argument is
+ /// null.</exception>
+ public ConcurrentQueue(IEnumerable<T> collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+
+ InitializeFromCollection(collection);
+ }
+
+ /// <summary>
+ /// Get the data array to be serialized
+ /// </summary>
+ [OnSerializing]
+ private void OnSerializing(StreamingContext context)
+ {
+ // save the data into the serialization array to be saved
+ _serializationArray = ToArray();
+ }
+
+ /// <summary>
+ /// Construct the queue from a previously serialized one
+ /// </summary>
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext context)
+ {
+ Debug.Assert(_serializationArray != null);
+ InitializeFromCollection(_serializationArray);
+ _serializationArray = null;
+ }
+
+ /// <summary>
+ /// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see
+ /// cref="T:System.Array"/>, starting at a particular
+ /// <see cref="T:System.Array"/> index.
+ /// </summary>
+ /// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the
+ /// destination of the elements copied from the
+ /// <see cref="T:System.Collections.Concurrent.ConcurrentBag"/>. The <see
+ /// cref="T:System.Array">Array</see> must have zero-based indexing.</param>
+ /// <param name="index">The zero-based index in <paramref name="array"/> at which copying
+ /// begins.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
+ /// Visual Basic).</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
+ /// zero.</exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="array"/> is multidimensional. -or-
+ /// <paramref name="array"/> does not have zero-based indexing. -or-
+ /// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/>
+ /// -or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is
+ /// greater than the available space from <paramref name="index"/> to the end of the destination
+ /// <paramref name="array"/>. -or- The type of the source <see
+ /// cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the
+ /// destination <paramref name="array"/>.
+ /// </exception>
+ void ICollection.CopyTo(Array array, int index)
+ {
+ // Validate arguments.
+ if (array == null)
+ {
+ throw new ArgumentNullException("array");
+ }
+
+ // We must be careful not to corrupt the array, so we will first accumulate an
+ // internal list of elements that we will then copy to the array. This requires
+ // some extra allocation, but is necessary since we don't know up front whether
+ // the array is sufficiently large to hold the stack's contents.
+ ((ICollection)ToList()).CopyTo(array, index);
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is
+ /// synchronized with the SyncRoot.
+ /// </summary>
+ /// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized
+ /// with the SyncRoot; otherwise, false. For <see cref="ConcurrentQueue{T}"/>, this property always
+ /// returns false.</value>
+ bool ICollection.IsSynchronized
+ {
+ // Gets a value indicating whether access to this collection is synchronized. Always returns
+ // false. The reason is subtle. While access is in face thread safe, it's not the case that
+ // locking on the SyncRoot would have prevented concurrent pushes and pops, as this property
+ // would typically indicate; that's because we internally use CAS operations vs. true locks.
+ get { return false; }
+ }
+
+
+ /// <summary>
+ /// Gets an object that can be used to synchronize access to the <see
+ /// cref="T:System.Collections.ICollection"/>. This property is not supported.
+ /// </summary>
+ /// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported.</exception>
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ throw new NotSupportedException(SR.ConcurrentCollection_SyncRoot_NotSupported);
+ }
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through a collection.
+ /// </summary>
+ /// <returns>An <see cref="T:System.Collections.IEnumerator"/> that can be used to iterate through the collection.</returns>
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable<T>)this).GetEnumerator();
+ }
+
+ /// <summary>
+ /// Attempts to add an object to the <see
+ /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>.
+ /// </summary>
+ /// <param name="item">The object to add to the <see
+ /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. The value can be a null
+ /// reference (Nothing in Visual Basic) for reference types.
+ /// </param>
+ /// <returns>true if the object was added successfully; otherwise, false.</returns>
+ /// <remarks>For <see cref="ConcurrentQueue{T}"/>, this operation will always add the object to the
+ /// end of the <see cref="ConcurrentQueue{T}"/>
+ /// and return true.</remarks>
+ bool IProducerConsumerCollection<T>.TryAdd(T item)
+ {
+ Enqueue(item);
+ return true;
+ }
+
+ /// <summary>
+ /// Attempts to remove and return an object from the <see
+ /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>.
+ /// </summary>
+ /// <param name="item">
+ /// When this method returns, if the operation was successful, <paramref name="item"/> contains the
+ /// object removed. If no object was available to be removed, the value is unspecified.
+ /// </param>
+ /// <returns>true if an element was removed and returned successfully; otherwise, false.</returns>
+ /// <remarks>For <see cref="ConcurrentQueue{T}"/>, this operation will attempt to remove the object
+ /// from the beginning of the <see cref="ConcurrentQueue{T}"/>.
+ /// </remarks>
+ bool IProducerConsumerCollection<T>.TryTake(out T item)
+ {
+ return TryDequeue(out item);
+ }
+
+ /// <summary>
+ /// Gets a value that indicates whether the <see cref="ConcurrentQueue{T}"/> is empty.
+ /// </summary>
+ /// <value>true if the <see cref="ConcurrentQueue{T}"/> is empty; otherwise, false.</value>
+ /// <remarks>
+ /// For determining whether the collection contains any items, use of this property is recommended
+ /// rather than retrieving the number of items from the <see cref="Count"/> property and comparing it
+ /// to 0. However, as this collection is intended to be accessed concurrently, it may be the case
+ /// that another thread will modify the collection after <see cref="IsEmpty"/> returns, thus invalidating
+ /// the result.
+ /// </remarks>
+ public bool IsEmpty
+ {
+ get
+ {
+ Segment head = _head;
+ if (!head.IsEmpty)
+ //fast route 1:
+ //if current head is not empty, then queue is not empty
+ return false;
+ else if (head.Next == null)
+ //fast route 2:
+ //if current head is empty and it's the last segment
+ //then queue is empty
+ return true;
+ else
+ //slow route:
+ //current head is empty and it is NOT the last segment,
+ //it means another thread is growing new segment
+ {
+ SpinWait spin = new SpinWait();
+ while (head.IsEmpty)
+ {
+ if (head.Next == null)
+ return true;
+
+ spin.SpinOnce();
+ head = _head;
+ }
+ return false;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies the elements stored in the <see cref="ConcurrentQueue{T}"/> to a new array.
+ /// </summary>
+ /// <returns>A new array containing a snapshot of elements copied from the <see
+ /// cref="ConcurrentQueue{T}"/>.</returns>
+ public T[] ToArray()
+ {
+ return ToList().ToArray();
+ }
+
+ /// <summary>
+ /// Copies the <see cref="ConcurrentQueue{T}"/> elements to a new <see
+ /// cref="T:System.Collections.Generic.List{T}"/>.
+ /// </summary>
+ /// <returns>A new <see cref="T:System.Collections.Generic.List{T}"/> containing a snapshot of
+ /// elements copied from the <see cref="ConcurrentQueue{T}"/>.</returns>
+ private List<T> ToList()
+ {
+ // Increments the number of active snapshot takers. This increment must happen before the snapshot is
+ // taken. At the same time, Decrement must happen after list copying is over. Only in this way, can it
+ // eliminate race condition when Segment.TryRemove() checks whether _numSnapshotTakers == 0.
+ Interlocked.Increment(ref _numSnapshotTakers);
+
+ List<T> list = new List<T>();
+ try
+ {
+ //store head and tail positions in buffer,
+ Segment head, tail;
+ int headLow, tailHigh;
+ GetHeadTailPositions(out head, out tail, out headLow, out tailHigh);
+
+ if (head == tail)
+ {
+ head.AddToList(list, headLow, tailHigh);
+ }
+ else
+ {
+ head.AddToList(list, headLow, SEGMENT_SIZE - 1);
+ Segment curr = head.Next;
+ while (curr != tail)
+ {
+ curr.AddToList(list, 0, SEGMENT_SIZE - 1);
+ curr = curr.Next;
+ }
+ //Add tail segment
+ tail.AddToList(list, 0, tailHigh);
+ }
+ }
+ finally
+ {
+ // This Decrement must happen after copying is over.
+ Interlocked.Decrement(ref _numSnapshotTakers);
+ }
+ return list;
+ }
+
+ /// <summary>
+ /// Store the position of the current head and tail positions.
+ /// </summary>
+ /// <param name="head">return the head segment</param>
+ /// <param name="tail">return the tail segment</param>
+ /// <param name="headLow">return the head offset, value range [0, SEGMENT_SIZE]</param>
+ /// <param name="tailHigh">return the tail offset, value range [-1, SEGMENT_SIZE-1]</param>
+ private void GetHeadTailPositions(out Segment head, out Segment tail,
+ out int headLow, out int tailHigh)
+ {
+ head = _head;
+ tail = _tail;
+ headLow = head.Low;
+ tailHigh = tail.High;
+ SpinWait spin = new SpinWait();
+
+ //we loop until the observed values are stable and sensible.
+ //This ensures that any update order by other methods can be tolerated.
+ while (
+ //if head and tail changed, retry
+ head != _head || tail != _tail
+ //if low and high pointers, retry
+ || headLow != head.Low || tailHigh != tail.High
+ //if head jumps ahead of tail because of concurrent grow and dequeue, retry
+ || head._index > tail._index)
+ {
+ spin.SpinOnce();
+ head = _head;
+ tail = _tail;
+ headLow = head.Low;
+ tailHigh = tail.High;
+ }
+ }
+
+
+ /// <summary>
+ /// Gets the number of elements contained in the <see cref="ConcurrentQueue{T}"/>.
+ /// </summary>
+ /// <value>The number of elements contained in the <see cref="ConcurrentQueue{T}"/>.</value>
+ /// <remarks>
+ /// For determining whether the collection contains any items, use of the <see cref="IsEmpty"/>
+ /// property is recommended rather than retrieving the number of items from the <see cref="Count"/>
+ /// property and comparing it to 0.
+ /// </remarks>
+ public int Count
+ {
+ get
+ {
+ //store head and tail positions in buffer,
+ Segment head, tail;
+ int headLow, tailHigh;
+ GetHeadTailPositions(out head, out tail, out headLow, out tailHigh);
+
+ if (head == tail)
+ {
+ return tailHigh - headLow + 1;
+ }
+
+ //head segment
+ int count = SEGMENT_SIZE - headLow;
+
+ //middle segment(s), if any, are full.
+ //We don't deal with overflow to be consistent with the behavior of generic types in CLR.
+ count += SEGMENT_SIZE * ((int)(tail._index - head._index - 1));
+
+ //tail segment
+ count += tailHigh + 1;
+
+ return count;
+ }
+ }
+
+
+ /// <summary>
+ /// Copies the <see cref="ConcurrentQueue{T}"/> elements to an existing one-dimensional <see
+ /// cref="T:System.Array">Array</see>, starting at the specified array index.
+ /// </summary>
+ /// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the
+ /// destination of the elements copied from the
+ /// <see cref="ConcurrentQueue{T}"/>. The <see cref="T:System.Array">Array</see> must have zero-based
+ /// indexing.</param>
+ /// <param name="index">The zero-based index in <paramref name="array"/> at which copying
+ /// begins.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
+ /// Visual Basic).</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
+ /// zero.</exception>
+ /// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the
+ /// length of the <paramref name="array"/>
+ /// -or- The number of elements in the source <see cref="ConcurrentQueue{T}"/> is greater than the
+ /// available space from <paramref name="index"/> to the end of the destination <paramref
+ /// name="array"/>.
+ /// </exception>
+ public void CopyTo(T[] array, int index)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException("array");
+ }
+
+ // We must be careful not to corrupt the array, so we will first accumulate an
+ // internal list of elements that we will then copy to the array. This requires
+ // some extra allocation, but is necessary since we don't know up front whether
+ // the array is sufficiently large to hold the stack's contents.
+ ToList().CopyTo(array, index);
+ }
+
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the <see
+ /// cref="ConcurrentQueue{T}"/>.
+ /// </summary>
+ /// <returns>An enumerator for the contents of the <see
+ /// cref="ConcurrentQueue{T}"/>.</returns>
+ /// <remarks>
+ /// The enumeration represents a moment-in-time snapshot of the contents
+ /// of the queue. It does not reflect any updates to the collection after
+ /// <see cref="GetEnumerator()"/> was called. The enumerator is safe to use
+ /// concurrently with reads from and writes to the queue.
+ /// </remarks>
+ public IEnumerator<T> GetEnumerator()
+ {
+ // Increments the number of active snapshot takers. This increment must happen before the snapshot is
+ // taken. At the same time, Decrement must happen after the enumeration is over. Only in this way, can it
+ // eliminate race condition when Segment.TryRemove() checks whether _numSnapshotTakers == 0.
+ Interlocked.Increment(ref _numSnapshotTakers);
+
+ // Takes a snapshot of the queue.
+ // A design flaw here: if a Thread.Abort() happens, we cannot decrement _numSnapshotTakers. But we cannot
+ // wrap the following with a try/finally block, otherwise the decrement will happen before the yield return
+ // statements in the GetEnumerator (head, tail, headLow, tailHigh) method.
+ Segment head, tail;
+ int headLow, tailHigh;
+ GetHeadTailPositions(out head, out tail, out headLow, out tailHigh);
+
+ //If we put yield-return here, the iterator will be lazily evaluated. As a result a snapshot of
+ // the queue is not taken when GetEnumerator is initialized but when MoveNext() is first called.
+ // This is inconsistent with existing generic collections. In order to prevent it, we capture the
+ // value of _head in a buffer and call out to a helper method.
+ //The old way of doing this was to return the ToList().GetEnumerator(), but ToList() was an
+ // unnecessary performance hit.
+ return GetEnumerator(head, tail, headLow, tailHigh);
+ }
+
+ /// <summary>
+ /// Helper method of GetEnumerator to separate out yield return statement, and prevent lazy evaluation.
+ /// </summary>
+ private IEnumerator<T> GetEnumerator(Segment head, Segment tail, int headLow, int tailHigh)
+ {
+ try
+ {
+ SpinWait spin = new SpinWait();
+
+ if (head == tail)
+ {
+ for (int i = headLow; i <= tailHigh; i++)
+ {
+ // If the position is reserved by an Enqueue operation, but the value is not written into,
+ // spin until the value is available.
+ spin.Reset();
+ while (!head._state[i]._value)
+ {
+ spin.SpinOnce();
+ }
+ yield return head._array[i];
+ }
+ }
+ else
+ {
+ //iterate on head segment
+ for (int i = headLow; i < SEGMENT_SIZE; i++)
+ {
+ // If the position is reserved by an Enqueue operation, but the value is not written into,
+ // spin until the value is available.
+ spin.Reset();
+ while (!head._state[i]._value)
+ {
+ spin.SpinOnce();
+ }
+ yield return head._array[i];
+ }
+ //iterate on middle segments
+ Segment curr = head.Next;
+ while (curr != tail)
+ {
+ for (int i = 0; i < SEGMENT_SIZE; i++)
+ {
+ // If the position is reserved by an Enqueue operation, but the value is not written into,
+ // spin until the value is available.
+ spin.Reset();
+ while (!curr._state[i]._value)
+ {
+ spin.SpinOnce();
+ }
+ yield return curr._array[i];
+ }
+ curr = curr.Next;
+ }
+
+ //iterate on tail segment
+ for (int i = 0; i <= tailHigh; i++)
+ {
+ // If the position is reserved by an Enqueue operation, but the value is not written into,
+ // spin until the value is available.
+ spin.Reset();
+ while (!tail._state[i]._value)
+ {
+ spin.SpinOnce();
+ }
+ yield return tail._array[i];
+ }
+ }
+ }
+ finally
+ {
+ // This Decrement must happen after the enumeration is over.
+ Interlocked.Decrement(ref _numSnapshotTakers);
+ }
+ }
+
+ /// <summary>
+ /// Adds an object to the end of the <see cref="ConcurrentQueue{T}"/>.
+ /// </summary>
+ /// <param name="item">The object to add to the end of the <see
+ /// cref="ConcurrentQueue{T}"/>. The value can be a null reference
+ /// (Nothing in Visual Basic) for reference types.
+ /// </param>
+ public void Enqueue(T item)
+ {
+ SpinWait spin = new SpinWait();
+ while (true)
+ {
+ Segment tail = _tail;
+ if (tail.TryAppend(item))
+ return;
+ spin.SpinOnce();
+ }
+ }
+
+
+ /// <summary>
+ /// Attempts to remove and return the object at the beginning of the <see
+ /// cref="ConcurrentQueue{T}"/>.
+ /// </summary>
+ /// <param name="result">
+ /// When this method returns, if the operation was successful, <paramref name="result"/> contains the
+ /// object removed. If no object was available to be removed, the value is unspecified.
+ /// </param>
+ /// <returns>true if an element was removed and returned from the beginning of the <see
+ /// cref="ConcurrentQueue{T}"/>
+ /// successfully; otherwise, false.</returns>
+ public bool TryDequeue(out T result)
+ {
+ while (!IsEmpty)
+ {
+ Segment head = _head;
+ if (head.TryRemove(out result))
+ return true;
+ //since method IsEmpty spins, we don't need to spin in the while loop
+ }
+ result = default(T);
+ return false;
+ }
+
+ /// <summary>
+ /// Attempts to return an object from the beginning of the <see cref="ConcurrentQueue{T}"/>
+ /// without removing it.
+ /// </summary>
+ /// <param name="result">When this method returns, <paramref name="result"/> contains an object from
+ /// the beginning of the <see cref="T:System.Collections.Concurrent.ConcurrentQueue{T}"/> or an
+ /// unspecified value if the operation failed.</param>
+ /// <returns>true if and object was returned successfully; otherwise, false.</returns>
+ public bool TryPeek(out T result)
+ {
+ Interlocked.Increment(ref _numSnapshotTakers);
+
+ while (!IsEmpty)
+ {
+ Segment head = _head;
+ if (head.TryPeek(out result))
+ {
+ Interlocked.Decrement(ref _numSnapshotTakers);
+ return true;
+ }
+ //since method IsEmpty spins, we don't need to spin in the while loop
+ }
+ result = default(T);
+ Interlocked.Decrement(ref _numSnapshotTakers);
+ return false;
+ }
+
+
+ /// <summary>
+ /// private class for ConcurrentQueue.
+ /// a queue is a linked list of small arrays, each node is called a segment.
+ /// A segment contains an array, a pointer to the next segment, and _low, _high indices recording
+ /// the first and last valid elements of the array.
+ /// </summary>
+ private class Segment
+ {
+ //we define two volatile arrays: _array and _state. Note that the accesses to the array items
+ //do not get volatile treatment. But we don't need to worry about loading adjacent elements or
+ //store/load on adjacent elements would suffer reordering.
+ // - Two stores: these are at risk, but CLRv2 memory model guarantees store-release hence we are safe.
+ // - Two loads: because one item from two volatile arrays are accessed, the loads of the array references
+ // are sufficient to prevent reordering of the loads of the elements.
+ internal volatile T[] _array;
+
+ // For each entry in _array, the corresponding entry in _state indicates whether this position contains
+ // a valid value. _state is initially all false.
+ internal volatile VolatileBool[] _state;
+
+ //pointer to the next segment. null if the current segment is the last segment
+ private volatile Segment _next;
+
+ //We use this zero based index to track how many segments have been created for the queue, and
+ //to compute how many active segments are there currently.
+ // * The number of currently active segments is : _tail._index - _head._index + 1;
+ // * _index is incremented with every Segment.Grow operation. We use Int64 type, and we can safely
+ // assume that it never overflows. To overflow, we need to do 2^63 increments, even at a rate of 4
+ // billion (2^32) increments per second, it takes 2^31 seconds, which is about 64 years.
+ internal readonly long _index;
+
+ //indices of where the first and last valid values
+ // - _low points to the position of the next element to pop from this segment, range [0, infinity)
+ // _low >= SEGMENT_SIZE implies the segment is disposable
+ // - _high points to the position of the latest pushed element, range [-1, infinity)
+ // _high == -1 implies the segment is new and empty
+ // _high >= SEGMENT_SIZE-1 means this segment is ready to grow.
+ // and the thread who sets _high to SEGMENT_SIZE-1 is responsible to grow the segment
+ // - Math.Min(_low, SEGMENT_SIZE) > Math.Min(_high, SEGMENT_SIZE-1) implies segment is empty
+ // - initially _low =0 and _high=-1;
+ private volatile int _low;
+ private volatile int _high;
+
+ private volatile ConcurrentQueue<T> _source;
+
+ /// <summary>
+ /// Create and initialize a segment with the specified index.
+ /// </summary>
+ internal Segment(long index, ConcurrentQueue<T> source)
+ {
+ _array = new T[SEGMENT_SIZE];
+ _state = new VolatileBool[SEGMENT_SIZE]; //all initialized to false
+ _high = -1;
+ Debug.Assert(index >= 0);
+ _index = index;
+ _source = source;
+ }
+
+ /// <summary>
+ /// return the next segment
+ /// </summary>
+ internal Segment Next
+ {
+ get { return _next; }
+ }
+
+
+ /// <summary>
+ /// return true if the current segment is empty (doesn't have any element available to dequeue,
+ /// false otherwise
+ /// </summary>
+ internal bool IsEmpty
+ {
+ get { return (Low > High); }
+ }
+
+ /// <summary>
+ /// Add an element to the tail of the current segment
+ /// exclusively called by ConcurrentQueue.InitializedFromCollection
+ /// InitializeFromCollection is responsible to guarantee that there is no index overflow,
+ /// and there is no contention
+ /// </summary>
+ /// <param name="value"></param>
+ internal void UnsafeAdd(T value)
+ {
+ Debug.Assert(_high < SEGMENT_SIZE - 1);
+ _high++;
+ _array[_high] = value;
+ _state[_high]._value = true;
+ }
+
+ /// <summary>
+ /// Create a new segment and append to the current one
+ /// Does not update the _tail pointer
+ /// exclusively called by ConcurrentQueue.InitializedFromCollection
+ /// InitializeFromCollection is responsible to guarantee that there is no index overflow,
+ /// and there is no contention
+ /// </summary>
+ /// <returns>the reference to the new Segment</returns>
+ internal Segment UnsafeGrow()
+ {
+ Debug.Assert(_high >= SEGMENT_SIZE - 1);
+ Segment newSegment = new Segment(_index + 1, _source); //_index is Int64, we don't need to worry about overflow
+ _next = newSegment;
+ return newSegment;
+ }
+
+ /// <summary>
+ /// Create a new segment and append to the current one
+ /// Update the _tail pointer
+ /// This method is called when there is no contention
+ /// </summary>
+ internal void Grow()
+ {
+ //no CAS is needed, since there is no contention (other threads are blocked, busy waiting)
+ Segment newSegment = new Segment(_index + 1, _source); //_index is Int64, we don't need to worry about overflow
+ _next = newSegment;
+ Debug.Assert(_source._tail == this);
+ _source._tail = _next;
+ }
+
+
+ /// <summary>
+ /// Try to append an element at the end of this segment.
+ /// </summary>
+ /// <param name="value">the element to append</param>
+ /// <returns>true if the element is appended, false if the current segment is full</returns>
+ /// <remarks>if appending the specified element succeeds, and after which the segment is full,
+ /// then grow the segment</remarks>
+ internal bool TryAppend(T value)
+ {
+ //quickly check if _high is already over the boundary, if so, bail out
+ if (_high >= SEGMENT_SIZE - 1)
+ {
+ return false;
+ }
+
+ //Now we will use a CAS to increment _high, and store the result in newhigh.
+ //Depending on how many free spots left in this segment and how many threads are doing this Increment
+ //at this time, the returning "newhigh" can be
+ // 1) < SEGMENT_SIZE - 1 : we took a spot in this segment, and not the last one, just insert the value
+ // 2) == SEGMENT_SIZE - 1 : we took the last spot, insert the value AND grow the segment
+ // 3) > SEGMENT_SIZE - 1 : we failed to reserve a spot in this segment, we return false to
+ // Queue.Enqueue method, telling it to try again in the next segment.
+
+ int newhigh = SEGMENT_SIZE; //initial value set to be over the boundary
+
+ //We need do Interlocked.Increment and value/state update in a finally block to ensure that they run
+ //without interuption. This is to prevent anything from happening between them, and another dequeue
+ //thread maybe spinning forever to wait for _state[] to be true;
+ try
+ { }
+ finally
+ {
+ newhigh = Interlocked.Increment(ref _high);
+ if (newhigh <= SEGMENT_SIZE - 1)
+ {
+ _array[newhigh] = value;
+ _state[newhigh]._value = true;
+ }
+
+ //if this thread takes up the last slot in the segment, then this thread is responsible
+ //to grow a new segment. Calling Grow must be in the finally block too for reliability reason:
+ //if thread abort during Grow, other threads will be left busy spinning forever.
+ if (newhigh == SEGMENT_SIZE - 1)
+ {
+ Grow();
+ }
+ }
+
+ //if newhigh <= SEGMENT_SIZE-1, it means the current thread successfully takes up a spot
+ return newhigh <= SEGMENT_SIZE - 1;
+ }
+
+
+ /// <summary>
+ /// try to remove an element from the head of current segment
+ /// </summary>
+ /// <param name="result">The result.</param>
+ /// <returns>return false only if the current segment is empty</returns>
+ internal bool TryRemove(out T result)
+ {
+ SpinWait spin = new SpinWait();
+ int lowLocal = Low, highLocal = High;
+ while (lowLocal <= highLocal)
+ {
+ //try to update _low
+ if (Interlocked.CompareExchange(ref _low, lowLocal + 1, lowLocal) == lowLocal)
+ {
+ //if the specified value is not available (this spot is taken by a push operation,
+ // but the value is not written into yet), then spin
+ SpinWait spinLocal = new SpinWait();
+ while (!_state[lowLocal]._value)
+ {
+ spinLocal.SpinOnce();
+ }
+ result = _array[lowLocal];
+
+ // If there is no other thread taking snapshot (GetEnumerator(), ToList(), etc), reset the deleted entry to null.
+ // It is ok if after this conditional check _numSnapshotTakers becomes > 0, because new snapshots won't include
+ // the deleted entry at _array[lowLocal].
+ if (_source._numSnapshotTakers <= 0)
+ {
+ _array[lowLocal] = default(T); //release the reference to the object.
+ }
+
+ //if the current thread sets _low to SEGMENT_SIZE, which means the current segment becomes
+ //disposable, then this thread is responsible to dispose this segment, and reset _head
+ if (lowLocal + 1 >= SEGMENT_SIZE)
+ {
+ // Invariant: we only dispose the current _head, not any other segment
+ // In usual situation, disposing a segment is simply setting _head to _head._next
+ // But there is one special case, where _head and _tail points to the same and ONLY
+ //segment of the queue: Another thread A is doing Enqueue and finds that it needs to grow,
+ //while the *current* thread is doing *this* Dequeue operation, and finds that it needs to
+ //dispose the current (and ONLY) segment. Then we need to wait till thread A finishes its
+ //Grow operation, this is the reason of having the following while loop
+ spinLocal = new SpinWait();
+ while (_next == null)
+ {
+ spinLocal.SpinOnce();
+ }
+ Debug.Assert(_source._head == this);
+ _source._head = _next;
+ }
+ return true;
+ }
+ else
+ {
+ //CAS failed due to contention: spin briefly and retry
+ spin.SpinOnce();
+ lowLocal = Low; highLocal = High;
+ }
+ }//end of while
+ result = default(T);
+ return false;
+ }
+
+ /// <summary>
+ /// try to peek the current segment
+ /// </summary>
+ /// <param name="result">holds the return value of the element at the head position,
+ /// value set to default(T) if there is no such an element</param>
+ /// <returns>true if there are elements in the current segment, false otherwise</returns>
+ internal bool TryPeek(out T result)
+ {
+ result = default(T);
+ int lowLocal = Low;
+ if (lowLocal > High)
+ return false;
+ SpinWait spin = new SpinWait();
+ while (!_state[lowLocal]._value)
+ {
+ spin.SpinOnce();
+ }
+ result = _array[lowLocal];
+ return true;
+ }
+
+ /// <summary>
+ /// Adds part or all of the current segment into a List.
+ /// </summary>
+ /// <param name="list">the list to which to add</param>
+ /// <param name="start">the start position</param>
+ /// <param name="end">the end position</param>
+ internal void AddToList(List<T> list, int start, int end)
+ {
+ for (int i = start; i <= end; i++)
+ {
+ SpinWait spin = new SpinWait();
+ while (!_state[i]._value)
+ {
+ spin.SpinOnce();
+ }
+ list.Add(_array[i]);
+ }
+ }
+
+ /// <summary>
+ /// return the position of the head of the current segment
+ /// Value range [0, SEGMENT_SIZE], if it's SEGMENT_SIZE, it means this segment is exhausted and thus empty
+ /// </summary>
+ internal int Low
+ {
+ get
+ {
+ return Math.Min(_low, SEGMENT_SIZE);
+ }
+ }
+
+ /// <summary>
+ /// return the logical position of the tail of the current segment
+ /// Value range [-1, SEGMENT_SIZE-1]. When it's -1, it means this is a new segment and has no elemnet yet
+ /// </summary>
+ internal int High
+ {
+ get
+ {
+ //if _high > SEGMENT_SIZE, it means it's out of range, we should return
+ //SEGMENT_SIZE-1 as the logical position
+ return Math.Min(_high, SEGMENT_SIZE - 1);
+ }
+ }
+ }
+ }//end of class Segment
+
+ /// <summary>
+ /// A wrapper struct for volatile bool, please note the copy of the struct it self will not be volatile
+ /// for example this statement will not include in volatile operation volatileBool1 = volatileBool2 the jit will copy the struct and will ignore the volatile
+ /// </summary>
+ struct VolatileBool
+ {
+ public VolatileBool(bool value)
+ {
+ _value = value;
+ }
+ public volatile bool _value;
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// DataflowEtwProvider.cs
+//
+//
+// EventSource for Dataflow.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Security;
+#if FEATURE_TRACING
+using System.Diagnostics.Tracing;
+#endif
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+#if FEATURE_TRACING
+ /// <summary>Provides an event source for tracing Dataflow information.</summary>
+ [EventSource(
+ Name = "System.Threading.Tasks.Dataflow.DataflowEventSource",
+ Guid = "16F53577-E41D-43D4-B47E-C17025BF4025",
+ LocalizationResources = "FxResources.System.Threading.Tasks.Dataflow.SR")]
+ internal sealed class DataflowEtwProvider : EventSource
+ {
+ /// <summary>
+ /// Defines the singleton instance for the dataflow ETW provider.
+ /// The dataflow provider GUID is {16F53577-E41D-43D4-B47E-C17025BF4025}.
+ /// </summary>
+ internal readonly static DataflowEtwProvider Log = new DataflowEtwProvider();
+ /// <summary>Prevent external instantiation. All logging should go through the Log instance.</summary>
+ private DataflowEtwProvider() { }
+
+ /// <summary>Enabled for all keywords.</summary>
+ private const EventKeywords ALL_KEYWORDS = (EventKeywords)(-1);
+
+ //-----------------------------------------------------------------------------------
+ //
+ // Dataflow Event IDs (must be unique)
+ //
+
+ /// <summary>The event ID for when we encounter a new dataflow block object that hasn't had its name traced to the trace file.</summary>
+ private const int DATAFLOWBLOCKCREATED_EVENTID = 1;
+ /// <summary>The event ID for the task launched event.</summary>
+ private const int TASKLAUNCHED_EVENTID = 2;
+ /// <summary>The event ID for the block completed event.</summary>
+ private const int BLOCKCOMPLETED_EVENTID = 3;
+ /// <summary>The event ID for the block linked event.</summary>
+ private const int BLOCKLINKED_EVENTID = 4;
+ /// <summary>The event ID for the block unlinked event.</summary>
+ private const int BLOCKUNLINKED_EVENTID = 5;
+
+ //-----------------------------------------------------------------------------------
+ //
+ // Dataflow Events
+ //
+
+ #region Block Creation
+ /// <summary>Trace an event for when a new block is instantiated.</summary>
+ /// <param name="block">The dataflow block that was created.</param>
+ /// <param name="dataflowBlockOptions">The options with which the block was created.</param>
+ [NonEvent]
+ internal void DataflowBlockCreated(IDataflowBlock block, DataflowBlockOptions dataflowBlockOptions)
+ {
+ Contract.Requires(block != null, "Block needed for the ETW event.");
+ Contract.Requires(dataflowBlockOptions != null, "Options needed for the ETW event.");
+
+ if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
+ {
+ DataflowBlockCreated(
+ Common.GetNameForDebugger(block, dataflowBlockOptions),
+ Common.GetBlockId(block));
+ }
+ }
+
+ [Event(DATAFLOWBLOCKCREATED_EVENTID, Level = EventLevel.Informational)]
+ private void DataflowBlockCreated(string blockName, int blockId)
+ {
+ WriteEvent(DATAFLOWBLOCKCREATED_EVENTID, blockName, blockId);
+ }
+ #endregion
+
+ #region Task Launching
+ /// <summary>Trace an event for a block launching a task to handle messages.</summary>
+ /// <param name="block">The owner block launching a task.</param>
+ /// <param name="task">The task being launched for processing.</param>
+ /// <param name="reason">The reason the task is being launched.</param>
+ /// <param name="availableMessages">The number of messages available to be handled by the task.</param>
+ [NonEvent]
+ internal void TaskLaunchedForMessageHandling(
+ IDataflowBlock block, Task task, TaskLaunchedReason reason, int availableMessages)
+ {
+ Contract.Requires(block != null, "Block needed for the ETW event.");
+ Contract.Requires(task != null, "Task needed for the ETW event.");
+ Contract.Requires(reason == TaskLaunchedReason.ProcessingInputMessages || reason == TaskLaunchedReason.OfferingOutputMessages,
+ "The reason should be a supported value from the TaskLaunchedReason enumeration.");
+ if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
+ {
+ TaskLaunchedForMessageHandling(Common.GetBlockId(block), reason, availableMessages, task.Id);
+ }
+ }
+
+ [ThreadStatic]
+ private static object[] t_sharedArray;
+
+ [Event(TASKLAUNCHED_EVENTID, Level = EventLevel.Informational)]
+ private void TaskLaunchedForMessageHandling(int blockId, TaskLaunchedReason reason, int availableMessages, int taskId)
+ {
+ // There is no explicit WriteEvent() overload matching this event's fields:
+ // WriteEvent(TASKLAUNCHED_EVENTID, blockId, (int)reason, availableMessages, taskId);
+ // Therefore this call would hit the "params" overload, which leads to multiple object
+ // allocations every time this event is fired.
+
+ if (t_sharedArray == null)
+ {
+ t_sharedArray = new object[4];
+ }
+ t_sharedArray[0] = blockId;
+ t_sharedArray[1] = (int)reason;
+ t_sharedArray[2] = availableMessages;
+ t_sharedArray[3] = taskId;
+
+ WriteEvent(TASKLAUNCHED_EVENTID, t_sharedArray);
+ }
+
+ /// <summary>Describes the reason a task is being launched.</summary>
+ internal enum TaskLaunchedReason
+ {
+ /// <summary>A task is being launched to process incoming messages.</summary>
+ ProcessingInputMessages = 1,
+ /// <summary>A task is being launched to offer outgoing messages to linked targets.</summary>
+ OfferingOutputMessages = 2,
+ }
+ #endregion
+
+ #region Block Completion
+ /// <summary>Trace an event for a block completing.</summary>
+ /// <param name="block">The block that's completing.</param>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [NonEvent]
+ internal void DataflowBlockCompleted(IDataflowBlock block)
+ {
+ Contract.Requires(block != null, "Block needed for the ETW event.");
+ if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
+ {
+ Task completionTask = Common.GetPotentiallyNotSupportedCompletionTask(block);
+ bool blockIsCompleted = completionTask != null && completionTask.IsCompleted;
+ Debug.Assert(blockIsCompleted, "Block must be completed for this event to be valid.");
+ if (blockIsCompleted)
+ {
+ var reason = (BlockCompletionReason)completionTask.Status;
+ string exceptionData = string.Empty;
+
+ if (completionTask.IsFaulted)
+ {
+ try { exceptionData = string.Join(Environment.NewLine, completionTask.Exception.InnerExceptions.Select(e => e.ToString())); }
+ catch { }
+ }
+
+ DataflowBlockCompleted(Common.GetBlockId(block), reason, exceptionData);
+ }
+ }
+ }
+
+ /// <summary>Describes the reason a block completed.</summary>
+ internal enum BlockCompletionReason
+ {
+ /// <summary>The block completed successfully.</summary>
+ RanToCompletion = (int)TaskStatus.RanToCompletion,
+ /// <summary>The block completed due to an error.</summary>
+ Faulted = (int)TaskStatus.Faulted,
+ /// <summary>The block completed due to cancellation.</summary>
+ Canceled = (int)TaskStatus.Canceled
+ }
+
+ [Event(BLOCKCOMPLETED_EVENTID, Level = EventLevel.Informational)]
+ private void DataflowBlockCompleted(int blockId, BlockCompletionReason reason, string exceptionData)
+ {
+ WriteEvent(BLOCKCOMPLETED_EVENTID, blockId, (int)reason, exceptionData);
+ }
+ #endregion
+
+ #region Linking
+ /// <summary>Trace an event for a block linking.</summary>
+ /// <param name="source">The source block linking to a target.</param>
+ /// <param name="target">The target block being linked from a source.</param>
+ [NonEvent]
+ internal void DataflowBlockLinking<T>(ISourceBlock<T> source, ITargetBlock<T> target)
+ {
+ Contract.Requires(source != null, "Source needed for the ETW event.");
+ Contract.Requires(target != null, "Target needed for the ETW event.");
+ if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
+ {
+ DataflowBlockLinking(Common.GetBlockId(source), Common.GetBlockId(target));
+ }
+ }
+
+ [Event(BLOCKLINKED_EVENTID, Level = EventLevel.Informational)]
+ private void DataflowBlockLinking(int sourceId, int targetId)
+ {
+ WriteEvent(BLOCKLINKED_EVENTID, sourceId, targetId);
+ }
+ #endregion
+
+ #region Unlinking
+ /// <summary>Trace an event for a block unlinking.</summary>
+ /// <param name="source">The source block unlinking from a target.</param>
+ /// <param name="target">The target block being unlinked from a source.</param>
+ [NonEvent]
+ internal void DataflowBlockUnlinking<T>(ISourceBlock<T> source, ITargetBlock<T> target)
+ {
+ Contract.Requires(source != null, "Source needed for the ETW event.");
+ Contract.Requires(target != null, "Target needed for the ETW event.");
+ if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
+ {
+ // Try catch exists to prevent against faulty blocks or blocks that only partially implement the interface
+ DataflowBlockUnlinking(Common.GetBlockId(source), Common.GetBlockId(target));
+ }
+ }
+
+ [Event(BLOCKUNLINKED_EVENTID, Level = EventLevel.Informational)]
+ private void DataflowBlockUnlinking(int sourceId, int targetId)
+ {
+ WriteEvent(BLOCKUNLINKED_EVENTID, sourceId, targetId);
+ }
+ #endregion
+ }
+#endif
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// EnumerableDebugView.cs
+//
+//
+// Debugger type proxy for enumerables.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Linq;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Debugger type proxy for an enumerable of T.</summary>
+ internal sealed class EnumerableDebugView<TKey, TValue>
+ {
+ /// <summary>The enumerable being visualized.</summary>
+ private readonly IEnumerable<KeyValuePair<TKey, TValue>> _enumerable;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="enumerable">The enumerable being debugged.</param>
+ public EnumerableDebugView(IEnumerable<KeyValuePair<TKey, TValue>> enumerable)
+ {
+ Contract.Requires(enumerable != null, "Expected a non-null enumerable.");
+ _enumerable = enumerable;
+ }
+
+ /// <summary>Gets the contents of the list.</summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public KeyValuePair<TKey, TValue>[] Items { get { return _enumerable.ToArray(); } }
+ }
+
+ /// <summary>Debugger type proxy for an enumerable of T.</summary>
+ internal sealed class EnumerableDebugView<T>
+ {
+ /// <summary>The enumerable being visualized.</summary>
+ private readonly IEnumerable<T> _enumerable;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="enumerable">The enumerable being debugged.</param>
+ public EnumerableDebugView(IEnumerable<T> enumerable)
+ {
+ Contract.Requires(enumerable != null, "Expected a non-null enumerable.");
+ _enumerable = enumerable;
+ }
+
+ /// <summary>Gets the contents of the list.</summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items { get { return _enumerable.ToArray(); } }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// IDebuggerDisplay.cs
+//
+//
+// An interface implemented by objects that expose their debugger display
+// attribute content through a property, making it possible for code to query
+// for the same content.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Implemented to provide customizable data for debugger displays.</summary>
+ internal interface IDebuggerDisplay
+ {
+ /// <summary>The object to be displayed as the content of a DebuggerDisplayAttribute.</summary>
+ /// <remarks>
+ /// The property returns an object to allow the debugger to interpret arbitrary .NET objects.
+ /// The return value may be, but need not be limited to be, a string.
+ /// </remarks>
+ object Content { get; }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// IProducerConsumerCollection.cs
+//
+//
+// A common interface for all concurrent collections.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Threading.Tasks.Dataflow.Internal.Collections
+{
+ /// <summary>
+ /// Defines methods to manipulate thread-safe collections intended for producer/consumer usage.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of elements in the collection.</typeparam>
+ /// <remarks>
+ /// All implementations of this interface must enable all members of this interface
+ /// to be used concurrently from multiple threads.
+ /// </remarks>
+ internal interface IProducerConsumerCollection<T> : IEnumerable<T>, ICollection
+ {
+ /// <summary>
+ /// Copies the elements of the <see cref="IProducerConsumerCollection{T}"/> to
+ /// an
+ /// <see cref="T:System.Array"/>, starting at a specified index.
+ /// </summary>
+ /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of
+ /// the elements copied from the <see cref="IProducerConsumerCollection{T}"/>.
+ /// The array must have zero-based indexing.</param>
+ /// <param name="index">The zero-based index in <paramref name="array"/> at which copying
+ /// begins.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
+ /// Visual Basic).</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
+ /// zero.</exception>
+ /// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the
+ /// length of the <paramref name="array"/>
+ /// -or- The number of elements in the source <see cref="ConcurrentQueue{T}"/> is greater than the
+ /// available space from <paramref name="index"/> to the end of the destination <paramref
+ /// name="array"/>.
+ /// </exception>
+ void CopyTo(T[] array, int index);
+
+ /// <summary>
+ /// Attempts to add an object to the <see
+ /// cref="IProducerConsumerCollection{T}"/>.
+ /// </summary>
+ /// <param name="item">The object to add to the <see
+ /// cref="IProducerConsumerCollection{T}"/>.</param>
+ /// <returns>true if the object was added successfully; otherwise, false.</returns>
+ /// <exception cref="T:System.ArgumentException">The <paramref name="item"/> was invalid for this collection.</exception>
+ bool TryAdd(T item);
+
+ /// <summary>
+ /// Attempts to remove and return an object from the <see cref="IProducerConsumerCollection{T}"/>.
+ /// </summary>
+ /// <param name="item">
+ /// When this method returns, if the object was removed and returned successfully, <paramref
+ /// name="item"/> contains the removed object. If no object was available to be removed, the value is
+ /// unspecified.
+ /// </param>
+ /// <returns>true if an object was removed and returned successfully; otherwise, false.</returns>
+ bool TryTake(out T item);
+
+ /// <summary>
+ /// Copies the elements contained in the <see cref="IProducerConsumerCollection{T}"/> to a new array.
+ /// </summary>
+ /// <returns>A new array containing the elements copied from the <see cref="IProducerConsumerCollection{T}"/>.</returns>
+ T[] ToArray();
+ }
+
+
+ /// <summary>
+ /// A debugger view of the IProducerConsumerCollection that makes it simple to browse the
+ /// collection's contents at a point in time.
+ /// </summary>
+ /// <typeparam name="T">The type of elements stored within.</typeparam>
+ internal sealed class SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<T>
+ {
+ private IProducerConsumerCollection<T> _collection; // The collection being viewed.
+
+ /// <summary>
+ /// Constructs a new debugger view object for the provided collection object.
+ /// </summary>
+ /// <param name="collection">A collection to browse in the debugger.</param>
+ public SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView(IProducerConsumerCollection<T> collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+
+ _collection = collection;
+ }
+
+ /// <summary>
+ /// Returns a snapshot of the underlying collection's elements.
+ /// </summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items
+ {
+ get { return _collection.ToArray(); }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ImmutableList.cs
+//
+//
+// An immutable data structure that supports adding, removing, and enumerating elements.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Diagnostics;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Provides a simple, immutable list.</summary>
+ /// <typeparam name="T">Specifies the type of the data stored in the list.</typeparam>
+ [DebuggerDisplay("Count={Count}")]
+ [DebuggerTypeProxy(typeof(EnumerableDebugView<>))]
+ internal sealed class ImmutableList<T> : IEnumerable<T>
+ {
+ /// <summary>An empty list.</summary>
+ private readonly static ImmutableList<T> _empty = new ImmutableList<T>();
+ /// <summary>The immutable data in this list instance.</summary>
+ private readonly T[] _array;
+
+ /// <summary>Gets the empty list.</summary>
+ public static ImmutableList<T> Empty { get { return _empty; } }
+
+ /// <summary>Initializes the immutable list to be empty.</summary>
+ private ImmutableList() : this(new T[0]) { }
+
+ /// <summary>Initializes the immutable list with the specified elements.</summary>
+ /// <param name="elements">The element array to use for this list's data.</param>
+ private ImmutableList(T[] elements)
+ {
+ Contract.Requires(elements != null, "List requires an array to wrap.");
+ _array = elements;
+ }
+
+ /// <summary>Creates a new immutable list from this list and the additional element.</summary>
+ /// <param name="item">The item to add.</param>
+ /// <returns>The new list.</returns>
+ public ImmutableList<T> Add(T item)
+ {
+ // Copy the elements from this list and the item
+ // to a new list that's returned.
+ var newArray = new T[_array.Length + 1];
+ Array.Copy(_array, 0, newArray, 0, _array.Length);
+ newArray[newArray.Length - 1] = item;
+ return new ImmutableList<T>(newArray);
+ }
+
+ /// <summary>Creates a new immutable list from this list and without the specified element.</summary>
+ /// <param name="item">The item to remove.</param>
+ /// <returns>The new list.</returns>
+ public ImmutableList<T> Remove(T item)
+ {
+ // Get the index of the element. If it's not in the list, just return this list.
+ int index = Array.IndexOf(_array, item);
+ if (index < 0) return this;
+
+ // It's in the list, so if it's the only one, just return the empty list
+ if (_array.Length == 1) return Empty;
+
+ // Otherwise, copy the other elements to a new list that's returned.
+ var newArray = new T[_array.Length - 1];
+ Array.Copy(_array, 0, newArray, 0, index);
+ Array.Copy(_array, index + 1, newArray, index, _array.Length - index - 1);
+ return new ImmutableList<T>(newArray);
+ }
+
+ /// <summary>Gets the number of elements in this list.</summary>
+ public int Count { get { return _array.Length; } }
+
+ /// <summary>Gets whether the list contains the specified item.</summary>
+ /// <param name="item">The item to lookup.</param>
+ /// <returns>true if the list contains the item; otherwise, false.</returns>
+ public bool Contains(T item) { return Array.IndexOf(_array, item) >= 0; }
+
+ /// <summary>Returns an enumerator that iterates through the collection.</summary>
+ public IEnumerator<T> GetEnumerator() { return ((IEnumerable<T>)_array).GetEnumerator(); }
+ /// <summary>Returns an enumerator that iterates through the collection.</summary>
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// Padding.cs
+//
+//
+// Helper structs for padding over CPU cache lines to avoid false sharing.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Runtime.InteropServices;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>A placeholder class for common padding constants and eventually routines.</summary>
+ internal static class Padding
+ {
+ /// <summary>A size greater than or equal to the size of the most common CPU cache lines.</summary>
+ internal const int CACHE_LINE_SIZE = 128;
+ }
+
+ /// <summary>Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}.</summary>
+ [StructLayout(LayoutKind.Explicit, Size = Padding.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines
+ internal struct PaddingForInt32
+ {
+ }
+
+ /// <summary>Value type that contains single Int64 value padded on both sides.</summary>
+ [StructLayout(LayoutKind.Explicit, Size = 2 * Padding.CACHE_LINE_SIZE)]
+ internal struct PaddedInt64
+ {
+ [FieldOffset(Padding.CACHE_LINE_SIZE)]
+ internal Int64 Value;
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ProducerConsumerQueues.cs
+//
+//
+// Specialized producer/consumer queues.
+//
+//
+// ************<IMPORTANT NOTE>*************
+//
+// There are two exact copies of this file:
+// src\ndp\clr\src\bcl\system\threading\tasks\producerConsumerQueue.cs
+// src\ndp\fx\src\dataflow\system\threading\tasks\dataflow\internal\producerConsumerQueue.cs
+// Keep both of them consistent by changing the other file when you change this one, also avoid:
+// 1- To reference internal types in mscorlib
+// 2- To reference any dataflow specific types
+// This should be fixed post Dev11 when this class becomes public.
+//
+// ************</IMPORTANT NOTE>*************
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections;
+#if CONCURRENT_COLLECTIONS
+using System.Collections.Concurrent;
+#else
+using System.Threading.Tasks.Dataflow.Internal.Collections;
+#endif
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Runtime.InteropServices;
+
+namespace System.Threading.Tasks
+{
+ /// <summary>Represents a producer/consumer queue used internally by dataflow blocks.</summary>
+ /// <typeparam name="T">Specifies the type of data contained in the queue.</typeparam>
+ internal interface IProducerConsumerQueue<T> : IEnumerable<T>
+ {
+ /// <summary>Enqueues an item into the queue.</summary>
+ /// <param name="item">The item to enqueue.</param>
+ /// <remarks>This method is meant to be thread-safe subject to the particular nature of the implementation.</remarks>
+ void Enqueue(T item);
+
+ /// <summary>Attempts to dequeue an item from the queue.</summary>
+ /// <param name="result">The dequeued item.</param>
+ /// <returns>true if an item could be dequeued; otherwise, false.</returns>
+ /// <remarks>This method is meant to be thread-safe subject to the particular nature of the implementation.</remarks>
+ bool TryDequeue(out T result);
+
+ /// <summary>Gets whether the collection is currently empty.</summary>
+ /// <remarks>This method may or may not be thread-safe.</remarks>
+ bool IsEmpty { get; }
+
+ /// <summary>Gets the number of items in the collection.</summary>
+ /// <remarks>In many implementations, this method will not be thread-safe.</remarks>
+ int Count { get; }
+
+ /// <summary>A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object.</summary>
+ /// <param name="syncObj">The sync object used to lock</param>
+ /// <returns>The collection count</returns>
+ int GetCountSafe(object syncObj);
+ }
+
+ /// <summary>
+ /// Provides a producer/consumer queue safe to be used by any number of producers and consumers concurrently.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of data contained in the queue.</typeparam>
+ [DebuggerDisplay("Count = {Count}")]
+ internal sealed class MultiProducerMultiConsumerQueue<T> : ConcurrentQueue<T>, IProducerConsumerQueue<T>
+ {
+ /// <summary>Enqueues an item into the queue.</summary>
+ /// <param name="item">The item to enqueue.</param>
+ void IProducerConsumerQueue<T>.Enqueue(T item) { base.Enqueue(item); }
+
+ /// <summary>Attempts to dequeue an item from the queue.</summary>
+ /// <param name="result">The dequeued item.</param>
+ /// <returns>true if an item could be dequeued; otherwise, false.</returns>
+ bool IProducerConsumerQueue<T>.TryDequeue(out T result) { return base.TryDequeue(out result); }
+
+ /// <summary>Gets whether the collection is currently empty.</summary>
+ bool IProducerConsumerQueue<T>.IsEmpty { get { return base.IsEmpty; } }
+
+ /// <summary>Gets the number of items in the collection.</summary>
+ int IProducerConsumerQueue<T>.Count { get { return base.Count; } }
+
+ /// <summary>A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object.</summary>
+ /// <remarks>ConcurrentQueue.Count is thread safe, no need to acquire the lock.</remarks>
+ int IProducerConsumerQueue<T>.GetCountSafe(object syncObj) { return base.Count; }
+ }
+
+ /// <summary>
+ /// Provides a producer/consumer queue safe to be used by only one producer and one consumer concurrently.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of data contained in the queue.</typeparam>
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(SingleProducerSingleConsumerQueue<>.SingleProducerSingleConsumerQueue_DebugView))]
+ internal sealed class SingleProducerSingleConsumerQueue<T> : IProducerConsumerQueue<T>
+ {
+ // Design:
+ //
+ // SingleProducerSingleConsumerQueue (SPSCQueue) is a concurrent queue designed to be used
+ // by one producer thread and one consumer thread. SPSCQueue does not work correctly when used by
+ // multiple producer threads concurrently or multiple consumer threads concurrently.
+ //
+ // SPSCQueue is based on segments that behave like circular buffers. Each circular buffer is represented
+ // as an array with two indexes: _first and _last. _first is the index of the array slot for the consumer
+ // to read next, and _last is the slot for the producer to write next. The circular buffer is empty when
+ // (_first == _last), and full when ((_last+1) % _array.Length == _first).
+ //
+ // Since _first is only ever modified by the consumer thread and _last by the producer, the two indices can
+ // be updated without interlocked operations. As long as the queue size fits inside a single circular buffer,
+ // enqueues and dequeues simply advance the corresponding indices around the circular buffer. If an enqueue finds
+ // that there is no room in the existing buffer, however, a new circular buffer is allocated that is twice as big
+ // as the old buffer. From then on, the producer will insert values into the new buffer. The consumer will first
+ // empty out the old buffer and only then follow the producer into the new (larger) buffer.
+ //
+ // As described above, the enqueue operation on the fast path only modifies the _first field of the current segment.
+ // However, it also needs to read _last in order to verify that there is room in the current segment. Similarly, the
+ // dequeue operation on the fast path only needs to modify _last, but also needs to read _first to verify that the
+ // queue is non-empty. This results in true cache line sharing between the producer and the consumer.
+ //
+ // The cache line sharing issue can be mitigating by having a possibly stale copy of _first that is owned by the producer,
+ // and a possibly stale copy of _last that is owned by the consumer. So, the consumer state is described using
+ // (_first, _lastCopy) and the producer state using (_firstCopy, _last). The consumer state is separated from
+ // the producer state by padding, which allows fast-path enqueues and dequeues from hitting shared cache lines.
+ // _lastCopy is the consumer's copy of _last. Whenever the consumer can tell that there is room in the buffer
+ // simply by observing _lastCopy, the consumer thread does not need to read _last and thus encounter a cache miss. Only
+ // when the buffer appears to be empty will the consumer refresh _lastCopy from _last. _firstCopy is used by the producer
+ // in the same way to avoid reading _first on the hot path.
+
+ /// <summary>The initial size to use for segments (in number of elements).</summary>
+ private const int INIT_SEGMENT_SIZE = 32; // must be a power of 2
+ /// <summary>The maximum size to use for segments (in number of elements).</summary>
+ private const int MAX_SEGMENT_SIZE = 0x1000000; // this could be made as large as Int32.MaxValue / 2
+
+ /// <summary>The head of the linked list of segments.</summary>
+ private volatile Segment _head;
+ /// <summary>The tail of the linked list of segments.</summary>
+ private volatile Segment _tail;
+
+ /// <summary>Initializes the queue.</summary>
+ internal SingleProducerSingleConsumerQueue()
+ {
+ // Validate constants in ctor rather than in an explicit cctor that would cause perf degradation
+ Debug.Assert(INIT_SEGMENT_SIZE > 0, "Initial segment size must be > 0.");
+ Debug.Assert((INIT_SEGMENT_SIZE & (INIT_SEGMENT_SIZE - 1)) == 0, "Initial segment size must be a power of 2");
+ Debug.Assert(INIT_SEGMENT_SIZE <= MAX_SEGMENT_SIZE, "Initial segment size should be <= maximum.");
+ Debug.Assert(MAX_SEGMENT_SIZE < Int32.MaxValue / 2, "Max segment size * 2 must be < Int32.MaxValue, or else overflow could occur.");
+
+ // Initialize the queue
+ _head = _tail = new Segment(INIT_SEGMENT_SIZE);
+ }
+
+ /// <summary>Enqueues an item into the queue.</summary>
+ /// <param name="item">The item to enqueue.</param>
+ public void Enqueue(T item)
+ {
+ Segment segment = _tail;
+ T[] array = segment._array;
+ int last = segment._state._last; // local copy to avoid multiple volatile reads
+
+ // Fast path: there's obviously room in the current segment
+ int tail2 = (last + 1) & (array.Length - 1);
+ if (tail2 != segment._state._firstCopy)
+ {
+ array[last] = item;
+ segment._state._last = tail2;
+ }
+ // Slow path: there may not be room in the current segment.
+ else EnqueueSlow(item, ref segment);
+ }
+
+ /// <summary>Enqueues an item into the queue.</summary>
+ /// <param name="item">The item to enqueue.</param>
+ /// <param name="segment">The segment in which to first attempt to store the item.</param>
+ private void EnqueueSlow(T item, ref Segment segment)
+ {
+ Contract.Requires(segment != null, "Expected a non-null segment.");
+
+ if (segment._state._firstCopy != segment._state._first)
+ {
+ segment._state._firstCopy = segment._state._first;
+ Enqueue(item); // will only recur once for this enqueue operation
+ return;
+ }
+
+ int newSegmentSize = _tail._array.Length << 1; // double size
+ Debug.Assert(newSegmentSize > 0, "The max size should always be small enough that we don't overflow.");
+ if (newSegmentSize > MAX_SEGMENT_SIZE) newSegmentSize = MAX_SEGMENT_SIZE;
+
+ var newSegment = new Segment(newSegmentSize);
+ newSegment._array[0] = item;
+ newSegment._state._last = 1;
+ newSegment._state._lastCopy = 1;
+
+ try { }
+ finally
+ {
+ // Finally block to protect against corruption due to a thread abort
+ // between setting _next and setting _tail.
+ Volatile.Write(ref _tail._next, newSegment); // ensure segment not published until item is fully stored
+ _tail = newSegment;
+ }
+ }
+
+ /// <summary>Attempts to dequeue an item from the queue.</summary>
+ /// <param name="result">The dequeued item.</param>
+ /// <returns>true if an item could be dequeued; otherwise, false.</returns>
+ public bool TryDequeue(out T result)
+ {
+ Segment segment = _head;
+ T[] array = segment._array;
+ int first = segment._state._first; // local copy to avoid multiple volatile reads
+
+ // Fast path: there's obviously data available in the current segment
+ if (first != segment._state._lastCopy)
+ {
+ result = array[first];
+ array[first] = default(T); // Clear the slot to release the element
+ segment._state._first = (first + 1) & (array.Length - 1);
+ return true;
+ }
+ // Slow path: there may not be data available in the current segment
+ else return TryDequeueSlow(ref segment, ref array, out result);
+ }
+
+ /// <summary>Attempts to dequeue an item from the queue.</summary>
+ /// <param name="array">The array from which the item was dequeued.</param>
+ /// <param name="segment">The segment from which the item was dequeued.</param>
+ /// <param name="result">The dequeued item.</param>
+ /// <returns>true if an item could be dequeued; otherwise, false.</returns>
+ private bool TryDequeueSlow(ref Segment segment, ref T[] array, out T result)
+ {
+ Contract.Requires(segment != null, "Expected a non-null segment.");
+ Contract.Requires(array != null, "Expected a non-null item array.");
+
+ if (segment._state._last != segment._state._lastCopy)
+ {
+ segment._state._lastCopy = segment._state._last;
+ return TryDequeue(out result); // will only recur once for this dequeue operation
+ }
+
+ if (segment._next != null && segment._state._first == segment._state._last)
+ {
+ segment = segment._next;
+ array = segment._array;
+ _head = segment;
+ }
+
+ int first = segment._state._first; // local copy to avoid extraneous volatile reads
+
+ if (first == segment._state._last)
+ {
+ result = default(T);
+ return false;
+ }
+
+ result = array[first];
+ array[first] = default(T); // Clear the slot to release the element
+ segment._state._first = (first + 1) & (segment._array.Length - 1);
+ segment._state._lastCopy = segment._state._last; // Refresh _lastCopy to ensure that _first has not passed _lastCopy
+
+ return true;
+ }
+
+ /// <summary>Attempts to peek at an item in the queue.</summary>
+ /// <param name="result">The peeked item.</param>
+ /// <returns>true if an item could be peeked; otherwise, false.</returns>
+ public bool TryPeek(out T result)
+ {
+ Segment segment = _head;
+ T[] array = segment._array;
+ int first = segment._state._first; // local copy to avoid multiple volatile reads
+
+ // Fast path: there's obviously data available in the current segment
+ if (first != segment._state._lastCopy)
+ {
+ result = array[first];
+ return true;
+ }
+ // Slow path: there may not be data available in the current segment
+ else return TryPeekSlow(ref segment, ref array, out result);
+ }
+
+ /// <summary>Attempts to peek at an item in the queue.</summary>
+ /// <param name="array">The array from which the item is peeked.</param>
+ /// <param name="segment">The segment from which the item is peeked.</param>
+ /// <param name="result">The peeked item.</param>
+ /// <returns>true if an item could be peeked; otherwise, false.</returns>
+ private bool TryPeekSlow(ref Segment segment, ref T[] array, out T result)
+ {
+ Contract.Requires(segment != null, "Expected a non-null segment.");
+ Contract.Requires(array != null, "Expected a non-null item array.");
+
+ if (segment._state._last != segment._state._lastCopy)
+ {
+ segment._state._lastCopy = segment._state._last;
+ return TryPeek(out result); // will only recur once for this peek operation
+ }
+
+ if (segment._next != null && segment._state._first == segment._state._last)
+ {
+ segment = segment._next;
+ array = segment._array;
+ _head = segment;
+ }
+
+ int first = segment._state._first; // local copy to avoid extraneous volatile reads
+
+ if (first == segment._state._last)
+ {
+ result = default(T);
+ return false;
+ }
+
+ result = array[first];
+ return true;
+ }
+
+ /// <summary>Attempts to dequeue an item from the queue.</summary>
+ /// <param name="predicate">The predicate that must return true for the item to be dequeued. If null, all items implicitly return true.</param>
+ /// <param name="result">The dequeued item.</param>
+ /// <returns>true if an item could be dequeued; otherwise, false.</returns>
+ public bool TryDequeueIf(Predicate<T> predicate, out T result)
+ {
+ Segment segment = _head;
+ T[] array = segment._array;
+ int first = segment._state._first; // local copy to avoid multiple volatile reads
+
+ // Fast path: there's obviously data available in the current segment
+ if (first != segment._state._lastCopy)
+ {
+ result = array[first];
+ if (predicate == null || predicate(result))
+ {
+ array[first] = default(T); // Clear the slot to release the element
+ segment._state._first = (first + 1) & (array.Length - 1);
+ return true;
+ }
+ else
+ {
+ result = default(T);
+ return false;
+ }
+ }
+ // Slow path: there may not be data available in the current segment
+ else return TryDequeueIfSlow(predicate, ref segment, ref array, out result);
+ }
+
+ /// <summary>Attempts to dequeue an item from the queue.</summary>
+ /// <param name="predicate">The predicate that must return true for the item to be dequeued. If null, all items implicitly return true.</param>
+ /// <param name="array">The array from which the item was dequeued.</param>
+ /// <param name="segment">The segment from which the item was dequeued.</param>
+ /// <param name="result">The dequeued item.</param>
+ /// <returns>true if an item could be dequeued; otherwise, false.</returns>
+ private bool TryDequeueIfSlow(Predicate<T> predicate, ref Segment segment, ref T[] array, out T result)
+ {
+ Contract.Requires(segment != null, "Expected a non-null segment.");
+ Contract.Requires(array != null, "Expected a non-null item array.");
+
+ if (segment._state._last != segment._state._lastCopy)
+ {
+ segment._state._lastCopy = segment._state._last;
+ return TryDequeueIf(predicate, out result); // will only recur once for this dequeue operation
+ }
+
+ if (segment._next != null && segment._state._first == segment._state._last)
+ {
+ segment = segment._next;
+ array = segment._array;
+ _head = segment;
+ }
+
+ int first = segment._state._first; // local copy to avoid extraneous volatile reads
+
+ if (first == segment._state._last)
+ {
+ result = default(T);
+ return false;
+ }
+
+ result = array[first];
+ if (predicate == null || predicate(result))
+ {
+ array[first] = default(T); // Clear the slot to release the element
+ segment._state._first = (first + 1) & (segment._array.Length - 1);
+ segment._state._lastCopy = segment._state._last; // Refresh _lastCopy to ensure that _first has not passed _lastCopy
+ return true;
+ }
+ else
+ {
+ result = default(T);
+ return false;
+ }
+ }
+
+ public void Clear()
+ {
+ T ignored;
+ while (TryDequeue(out ignored)) ;
+ }
+
+ /// <summary>Gets whether the collection is currently empty.</summary>
+ /// <remarks>WARNING: This should not be used concurrently without further vetting.</remarks>
+ public bool IsEmpty
+ {
+ // This implementation is optimized for calls from the consumer.
+ get
+ {
+ Segment head = _head;
+ if (head._state._first != head._state._lastCopy) return false; // _first is volatile, so the read of _lastCopy cannot get reordered
+ if (head._state._first != head._state._last) return false;
+ return head._next == null;
+ }
+ }
+
+ /// <summary>Gets an enumerable for the collection.</summary>
+ /// <remarks>WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently.</remarks>
+ public IEnumerator<T> GetEnumerator()
+ {
+ for (Segment segment = _head; segment != null; segment = segment._next)
+ {
+ for (int pt = segment._state._first;
+ pt != segment._state._last;
+ pt = (pt + 1) & (segment._array.Length - 1))
+ {
+ yield return segment._array[pt];
+ }
+ }
+ }
+ /// <summary>Gets an enumerable for the collection.</summary>
+ /// <remarks>WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently.</remarks>
+ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
+
+ /// <summary>Gets the number of items in the collection.</summary>
+ /// <remarks>WARNING: This should only be used for debugging purposes. It is not meant to be used concurrently.</remarks>
+ public int Count
+ {
+ get
+ {
+ int count = 0;
+ for (Segment segment = _head; segment != null; segment = segment._next)
+ {
+ int arraySize = segment._array.Length;
+ int first, last;
+ while (true) // Count is not meant to be used concurrently, but this helps to avoid issues if it is
+ {
+ first = segment._state._first;
+ last = segment._state._last;
+ if (first == segment._state._first) break;
+ }
+ count += (last - first) & (arraySize - 1);
+ }
+ return count;
+ }
+ }
+
+ /// <summary>A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object.</summary>
+ /// <remarks>The Count is not thread safe, so we need to acquire the lock.</remarks>
+ int IProducerConsumerQueue<T>.GetCountSafe(object syncObj)
+ {
+ Debug.Assert(syncObj != null, "The syncObj parameter is null.");
+ lock (syncObj)
+ {
+ return Count;
+ }
+ }
+
+ /// <summary>A segment in the queue containing one or more items.</summary>
+ [StructLayout(LayoutKind.Sequential)]
+ private sealed class Segment
+ {
+ /// <summary>The next segment in the linked list of segments.</summary>
+ internal Segment _next;
+ /// <summary>The data stored in this segment.</summary>
+ internal readonly T[] _array;
+ /// <summary>Details about the segment.</summary>
+ internal SegmentState _state; // separated out to enable StructLayout attribute to take effect
+
+ /// <summary>Initializes the segment.</summary>
+ /// <param name="size">The size to use for this segment.</param>
+ internal Segment(int size)
+ {
+ Contract.Requires((size & (size - 1)) == 0, "Size must be a power of 2");
+ _array = new T[size];
+ }
+ }
+
+ /// <summary>Stores information about a segment.</summary>
+ [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing
+ private struct SegmentState
+ {
+ /// <summary>Padding to reduce false sharing between the segment's array and _first.</summary>
+ internal PaddingFor32 _pad0;
+
+ /// <summary>The index of the current head in the segment.</summary>
+ internal volatile int _first;
+ /// <summary>A copy of the current tail index.</summary>
+ internal int _lastCopy; // not volatile as read and written by the producer, except for IsEmpty, and there _lastCopy is only read after reading the volatile _first
+
+ /// <summary>Padding to reduce false sharing between the first and last.</summary>
+ internal PaddingFor32 _pad1;
+
+ /// <summary>A copy of the current head index.</summary>
+ internal int _firstCopy; // not volatile as only read and written by the consumer thread
+ /// <summary>The index of the current tail in the segment.</summary>
+ internal volatile int _last;
+
+ /// <summary>Padding to reduce false sharing with the last and what's after the segment.</summary>
+ internal PaddingFor32 _pad2;
+ }
+
+ /// <summary>Debugger type proxy for a SingleProducerSingleConsumerQueue of T.</summary>
+ private sealed class SingleProducerSingleConsumerQueue_DebugView
+ {
+ /// <summary>The queue being visualized.</summary>
+ private readonly SingleProducerSingleConsumerQueue<T> _queue;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="queue">The queue being debugged.</param>
+ public SingleProducerSingleConsumerQueue_DebugView(SingleProducerSingleConsumerQueue<T> queue)
+ {
+ Contract.Requires(queue != null, "Expected a non-null queue.");
+ _queue = queue;
+ }
+
+ /// <summary>Gets the contents of the list.</summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items
+ {
+ get
+ {
+ List<T> list = new List<T>();
+ foreach (T item in _queue)
+ list.Add(item);
+ return list.ToArray();
+ }
+ }
+ }
+ }
+
+
+ /// <summary>A placeholder class for common padding constants and eventually routines.</summary>
+ static class PaddingHelpers
+ {
+ /// <summary>A size greater than or equal to the size of the most common CPU cache lines.</summary>
+ internal const int CACHE_LINE_SIZE = 128;
+ }
+
+ /// <summary>Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}.</summary>
+ [StructLayout(LayoutKind.Explicit, Size = PaddingHelpers.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines
+ struct PaddingFor32
+ {
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// QueuedMap.cs
+//
+//
+// A key-value pair queue, where pushing an existing key into the collection overwrites
+// the existing value.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>
+ /// Provides a data structure that supports pushing and popping key/value pairs.
+ /// Pushing a key/value pair for which the key already exists results in overwriting
+ /// the existing key entry's value.
+ /// </summary>
+ /// <typeparam name="TKey">Specifies the type of keys in the map.</typeparam>
+ /// <typeparam name="TValue">Specifies the type of values in the map.</typeparam>
+ /// <remarks>This type is not thread-safe.</remarks>
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(EnumerableDebugView<,>))]
+ internal sealed class QueuedMap<TKey, TValue>
+ {
+ /// <summary>
+ /// A queue structure that uses an array-based list to store its items
+ /// and that supports overwriting elements at specific indices.
+ /// </summary>
+ /// <typeparam name="T">The type of the items storedin the queue</typeparam>
+ /// <remarks>This type is not thread-safe.</remarks>
+ private sealed class ArrayBasedLinkedQueue<T>
+ {
+ /// <summary>Terminator index.</summary>
+ private const int TERMINATOR_INDEX = -1;
+ /// <summary>
+ /// The queue where the items will be stored.
+ /// The key of each entry is the index of the next entry in the queue.
+ /// </summary>
+ private readonly List<KeyValuePair<int, T>> _storage;
+ /// <summary>Index of the first queue item.</summary>
+ private int _headIndex = TERMINATOR_INDEX;
+ /// <summary>Index of the last queue item.</summary>
+ private int _tailIndex = TERMINATOR_INDEX;
+ /// <summary>Index of the first free slot.</summary>
+ private int _freeIndex = TERMINATOR_INDEX;
+
+ /// <summary>Initializes the Queue instance.</summary>
+ internal ArrayBasedLinkedQueue()
+ {
+ _storage = new List<KeyValuePair<int, T>>();
+ }
+
+ /// <summary>Initializes the Queue instance.</summary>
+ /// <param name="capacity">The capacity of the internal storage.</param>
+ internal ArrayBasedLinkedQueue(int capacity)
+ {
+ _storage = new List<KeyValuePair<int, T>>(capacity);
+ }
+
+ /// <summary>Enqueues an item.</summary>
+ /// <param name="item">The item to be enqueued.</param>
+ /// <returns>The index of the slot where item was stored.</returns>
+ internal int Enqueue(T item)
+ {
+ int newIndex;
+
+ // If there is a free slot, reuse it
+ if (_freeIndex != TERMINATOR_INDEX)
+ {
+ Debug.Assert(0 <= _freeIndex && _freeIndex < _storage.Count, "Index is out of range.");
+ newIndex = _freeIndex;
+ _freeIndex = _storage[_freeIndex].Key;
+ _storage[newIndex] = new KeyValuePair<int, T>(TERMINATOR_INDEX, item);
+ }
+ // If there is no free slot, add one
+ else
+ {
+ newIndex = _storage.Count;
+ _storage.Add(new KeyValuePair<int, T>(TERMINATOR_INDEX, item));
+ }
+
+ if (_headIndex == TERMINATOR_INDEX)
+ {
+ // Point _headIndex to newIndex if the queue was empty
+ Debug.Assert(_tailIndex == TERMINATOR_INDEX, "If head indicates empty, so too should tail.");
+ _headIndex = newIndex;
+ }
+ else
+ {
+ // Point the tail slot to newIndex if the queue was not empty
+ Debug.Assert(_tailIndex != TERMINATOR_INDEX, "If head does not indicate empty, neither should tail.");
+ _storage[_tailIndex] = new KeyValuePair<int, T>(newIndex, _storage[_tailIndex].Value);
+ }
+
+ // Point the tail slot newIndex
+ _tailIndex = newIndex;
+
+ return newIndex;
+ }
+
+ /// <summary>Tries to dequeue an item.</summary>
+ /// <param name="item">The item that is dequeued.</param>
+ internal bool TryDequeue(out T item)
+ {
+ // If the queue is empty, just initialize the output item and return false
+ if (_headIndex == TERMINATOR_INDEX)
+ {
+ Debug.Assert(_tailIndex == TERMINATOR_INDEX, "If head indicates empty, so too should tail.");
+ item = default(T);
+ return false;
+ }
+
+ // If there are items in the queue, start with populating the output item
+ Debug.Assert(0 <= _headIndex && _headIndex < _storage.Count, "Head is out of range.");
+ item = _storage[_headIndex].Value;
+
+ // Move the popped slot to the head of the free list
+ int newHeadIndex = _storage[_headIndex].Key;
+ _storage[_headIndex] = new KeyValuePair<int, T>(_freeIndex, default(T));
+ _freeIndex = _headIndex;
+ _headIndex = newHeadIndex;
+ if (_headIndex == TERMINATOR_INDEX) _tailIndex = TERMINATOR_INDEX;
+
+ return true;
+ }
+
+ /// <summary>Replaces the item of a given slot.</summary>
+ /// <param name="index">The index of the slot where the value should be replaced.</param>
+ /// <param name="item">The item to be places.</param>
+ internal void Replace(int index, T item)
+ {
+ Debug.Assert(0 <= index && index < _storage.Count, "Index is out of range.");
+#if DEBUG
+ // Also assert that index does not belong to the list of free slots
+ for (int idx = _freeIndex; idx != TERMINATOR_INDEX; idx = _storage[idx].Key)
+ Debug.Assert(idx != index, "Index should not belong to the list of free slots.");
+#endif
+ _storage[index] = new KeyValuePair<int, T>(_storage[index].Key, item);
+ }
+
+ internal bool IsEmpty { get { return _headIndex == TERMINATOR_INDEX; } }
+ }
+
+ /// <summary>The queue of elements.</summary>
+ private readonly ArrayBasedLinkedQueue<KeyValuePair<TKey, TValue>> _queue;
+ /// <summary>A map from key to index into the list.</summary>
+ /// <remarks>The correctness of this map relies on the list only having elements removed from its end.</remarks>
+ private readonly Dictionary<TKey, int> _mapKeyToIndex;
+
+ /// <summary>Initializes the QueuedMap.</summary>
+ internal QueuedMap()
+ {
+ _queue = new ArrayBasedLinkedQueue<KeyValuePair<TKey, TValue>>();
+ _mapKeyToIndex = new Dictionary<TKey, int>();
+ }
+
+ /// <summary>Initializes the QueuedMap.</summary>
+ /// <param name="capacity">The initial capacity of the data structure.</param>
+ internal QueuedMap(int capacity)
+ {
+ _queue = new ArrayBasedLinkedQueue<KeyValuePair<TKey, TValue>>(capacity);
+ _mapKeyToIndex = new Dictionary<TKey, int>(capacity);
+ }
+
+ /// <summary>Pushes a key/value pair into the data structure.</summary>
+ /// <param name="key">The key for the pair.</param>
+ /// <param name="value">The value for the pair.</param>
+ internal void Push(TKey key, TValue value)
+ {
+ // Try to get the index of the key in the queue. If it's there, replace the value.
+ int indexOfKeyInQueue;
+ if (!_queue.IsEmpty && _mapKeyToIndex.TryGetValue(key, out indexOfKeyInQueue))
+ {
+ _queue.Replace(indexOfKeyInQueue, new KeyValuePair<TKey, TValue>(key, value));
+ }
+ // If it's not there, add it to the queue and then add the mapping.
+ else
+ {
+ indexOfKeyInQueue = _queue.Enqueue(new KeyValuePair<TKey, TValue>(key, value));
+ _mapKeyToIndex.Add(key, indexOfKeyInQueue);
+ }
+ }
+
+ /// <summary>Try to pop the next element from the data structure.</summary>
+ /// <param name="item">The popped pair.</param>
+ /// <returns>true if an item could be popped; otherwise, false.</returns>
+ internal bool TryPop(out KeyValuePair<TKey, TValue> item)
+ {
+ bool popped = _queue.TryDequeue(out item);
+ if (popped) _mapKeyToIndex.Remove(item.Key);
+ return popped;
+ }
+
+ /// <summary>Tries to pop one or more elements from the data structure.</summary>
+ /// <param name="items">The items array into which the popped elements should be stored.</param>
+ /// <param name="arrayOffset">The offset into the array at which to start storing popped items.</param>
+ /// <param name="count">The number of items to be popped.</param>
+ /// <returns>The number of items popped, which may be less than the requested number if fewer existed in the data structure.</returns>
+ internal int PopRange(KeyValuePair<TKey, TValue>[] items, int arrayOffset, int count)
+ {
+ // As this data structure is internal, only assert incorrect usage.
+ // If this were to ever be made public, these would need to be real argument checks.
+ Contract.Requires(items != null, "Requires non-null array to store into.");
+ Contract.Requires(count >= 0 && arrayOffset >= 0, "Count and offset must be non-negative");
+ Contract.Requires(arrayOffset + count >= 0, "Offset plus count overflowed");
+ Contract.Requires(arrayOffset + count <= items.Length, "Range must be within array size");
+
+ int actualCount = 0;
+ for (int i = arrayOffset; actualCount < count; i++, actualCount++)
+ {
+ KeyValuePair<TKey, TValue> item;
+ if (TryPop(out item)) items[i] = item;
+ else break;
+ }
+
+ return actualCount;
+ }
+
+ /// <summary>Gets the number of items in the data structure.</summary>
+ internal int Count { get { return _mapKeyToIndex.Count; } }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// ReorderingBuffer.cs
+//
+//
+// An intermediate buffer that ensures messages are output in the right order.
+// Used by blocks (e.g. TransformBlock, TransformManyBlock) when operating in
+// parallel modes that could result in messages being processed out of order.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Linq;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Base interface for reordering buffers.</summary>
+ internal interface IReorderingBuffer
+ {
+ /// <summary>Informs the reordering buffer not to expect the message with the specified id.</summary>
+ /// <param name="id">The id of the message to be ignored.</param>
+ void IgnoreItem(long id);
+ }
+
+ /// <summary>Provides a buffer that reorders items according to their incoming IDs.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data stored in the items being reordered.</typeparam>
+ /// <remarks>
+ /// This type expects the first item to be ID==0 and for all subsequent items
+ /// to increase IDs sequentially.
+ /// </remarks>
+ [DebuggerDisplay("Count={CountForDebugging}")]
+ [DebuggerTypeProxy(typeof(ReorderingBuffer<>.DebugView))]
+ internal sealed class ReorderingBuffer<TOutput> : IReorderingBuffer
+ {
+ /// <summary>The source that owns this reordering buffer.</summary>
+ private readonly object _owningSource;
+ /// <summary>A reordering buffer used when parallelism is employed and items may be completed out-of-order.</summary>
+ /// <remarks>Also serves as the sync object to protect the contents of this class.</remarks>
+ private readonly Dictionary<long, KeyValuePair<bool, TOutput>> _reorderingBuffer = new Dictionary<long, KeyValuePair<bool, TOutput>>();
+ /// <summary>Action used to output items in order.</summary>
+ private readonly Action<object, TOutput> _outputAction;
+ /// <summary>The ID of the next item that should be released from the reordering buffer.</summary>
+ private long _nextReorderedIdToOutput = 0;
+
+ /// <summary>Gets the object used to synchronize all access to the reordering buffer's internals.</summary>
+ private object ValueLock { get { return _reorderingBuffer; } }
+
+ /// <summary>Initializes the reordering buffer.</summary>
+ /// <param name="owningSource">The source that owns this reordering buffer.</param>
+ /// <param name="outputAction">The action to invoke when the next in-order item is available to be output.</param>
+ internal ReorderingBuffer(object owningSource, Action<object, TOutput> outputAction)
+ {
+ // Validate and store internal arguments
+ Contract.Requires(owningSource != null, "Buffer must be associated with a source.");
+ Contract.Requires(outputAction != null, "Action required for when items are to be released.");
+ _owningSource = owningSource;
+ _outputAction = outputAction;
+ }
+
+ /// <summary>Stores the next item as it completes processing.</summary>
+ /// <param name="id">The ID of the item.</param>
+ /// <param name="item">The completed item.</param>
+ /// <param name="itemIsValid">Specifies whether the item is valid (true) or just a placeholder (false).</param>
+ internal void AddItem(long id, TOutput item, bool itemIsValid)
+ {
+ Contract.Requires(id != Common.INVALID_REORDERING_ID, "This ID should never have been handed out.");
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ // This may be called concurrently, so protect the buffer...
+ lock (ValueLock)
+ {
+ // If this is the next item we need in our ordering, output it.
+ if (_nextReorderedIdToOutput == id)
+ {
+ OutputNextItem(item, itemIsValid);
+ }
+ // Otherwise, we're using reordering and we're not ready for this item yet, so store
+ // it until we can use it.
+ else
+ {
+ Debug.Assert((ulong)id > (ulong)_nextReorderedIdToOutput, "Duplicate id.");
+ _reorderingBuffer.Add(id, new KeyValuePair<bool, TOutput>(itemIsValid, item));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the specified id is next to be output, and if it is
+ /// and if the item is "trusted" (meaning it may be output into the output
+ /// action as-is), adds it.
+ /// </summary>
+ /// <param name="id">The id of the item.</param>
+ /// <param name="item">The item.</param>
+ /// <param name="isTrusted">
+ /// Whether to allow the item to be output directly if it is the next item.
+ /// </param>
+ /// <returns>
+ /// null if the item was added.
+ /// true if the item was not added but is next in line.
+ /// false if the item was not added and is not next in line.
+ /// </returns>
+ internal bool? AddItemIfNextAndTrusted(long id, TOutput item, bool isTrusted)
+ {
+ Contract.Requires(id != Common.INVALID_REORDERING_ID, "This ID should never have been handed out.");
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ lock (ValueLock)
+ {
+ // If this is in the next item, try to take the fast path.
+ if (_nextReorderedIdToOutput == id)
+ {
+ // If we trust this data structure to be stored as-is,
+ // output it immediately. Otherwise, return that it is
+ // next to be output.
+ if (isTrusted)
+ {
+ OutputNextItem(item, itemIsValid: true);
+ return null;
+ }
+ else return true;
+ }
+ else return false;
+ }
+ }
+
+ /// <summary>Informs the reordering buffer not to expect the message with the specified id.</summary>
+ /// <param name="id">The id of the message to be ignored.</param>
+ public void IgnoreItem(long id)
+ {
+ AddItem(id, default(TOutput), itemIsValid: false);
+ }
+
+ /// <summary>Outputs the item. The item must have already been confirmed to have the next ID.</summary>
+ /// <param name="theNextItem">The item to output.</param>
+ /// <param name="itemIsValid">Whether the item is valid.</param>
+ private void OutputNextItem(TOutput theNextItem, bool itemIsValid)
+ {
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+
+ // Note that we're now looking for a different item, and pass this one through.
+ // Then release any items which may be pending.
+ _nextReorderedIdToOutput++;
+ if (itemIsValid) _outputAction(_owningSource, theNextItem);
+
+ // Try to get the next available item from the buffer and output it. Continue to do so
+ // until we run out of items in the reordering buffer or don't yet have the next ID buffered.
+ KeyValuePair<bool, TOutput> nextOutputItemWithValidity;
+ while (_reorderingBuffer.TryGetValue(_nextReorderedIdToOutput, out nextOutputItemWithValidity))
+ {
+ _reorderingBuffer.Remove(_nextReorderedIdToOutput);
+ _nextReorderedIdToOutput++;
+ if (nextOutputItemWithValidity.Key) _outputAction(_owningSource, nextOutputItemWithValidity.Value);
+ }
+ }
+
+ /// <summary>Gets a item count for debugging purposes.</summary>
+ private int CountForDebugging { get { return _reorderingBuffer.Count; } }
+
+ /// <summary>Provides a debugger type proxy for the buffer.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The buffer being debugged.</summary>
+ private readonly ReorderingBuffer<TOutput> _buffer;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="buffer">The buffer being debugged.</param>
+ public DebugView(ReorderingBuffer<TOutput> buffer)
+ {
+ Contract.Requires(buffer != null, "Need a buffer with which to construct the debug view.");
+ _buffer = buffer;
+ }
+
+ /// <summary>Gets a dictionary of buffered items and their reordering IDs.</summary>
+ public Dictionary<long, KeyValuePair<Boolean, TOutput>> ItemsBuffered { get { return _buffer._reorderingBuffer; } }
+ /// <summary>Gets the next ID required for outputting.</summary>
+ public long NextIdRequired { get { return _buffer._nextReorderedIdToOutput; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// SourceCore.cs
+//
+//
+// The core implementation of a standard ISourceBlock<TOutput>.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Security;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ // LOCK-LEVELING SCHEME
+ // --------------------
+ // SourceCore employs two locks: OutgoingLock and ValueLock. Additionally, targets we call out to
+ // likely utilize their own IncomingLock. We can hold OutgoingLock while acquiring ValueLock or IncomingLock.
+ // However, we cannot hold ValueLock while calling out to external code or while acquiring OutgoingLock, and
+ // we cannot hold IncomingLock when acquiring OutgoingLock. Additionally, the locks employed must be reentrant.
+
+ /// <summary>Provides a core implementation for blocks that implement <see cref="ISourceBlock{TOutput}"/>.</summary>
+ /// <typeparam name="TOutput">Specifies the type of data supplied by the <see cref="SourceCore{TOutput}"/>.</typeparam>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ internal sealed class SourceCore<TOutput>
+ {
+ // *** These fields are readonly and are initialized to new instances at construction.
+
+ /// <summary>A TaskCompletionSource that represents the completion of this block.</summary>
+ private readonly TaskCompletionSource<VoidResult> _completionTask = new TaskCompletionSource<VoidResult>();
+ /// <summary>A registry used to store all linked targets and information about them.</summary>
+ private readonly TargetRegistry<TOutput> _targetRegistry;
+ /// <summary>The output messages queued up to be received by consumers/targets.</summary>
+ /// <remarks>
+ /// The queue is only ever accessed by a single producer and single consumer at a time. On the producer side,
+ /// we require that AddMessage/AddMessages are the only places the queue is added to, and we require that those
+ /// methods not be used concurrently with anything else. All of our target halves today follow that restriction;
+ /// for example, TransformBlock with DOP==1 will have at most a single task processing the user provided delegate,
+ /// and thus at most one task calling AddMessage. If it has a DOP > 1, it'll go through the ReorderingBuffer,
+ /// which will use a lock to synchronize the output of all of the processing tasks such that only one is using
+ /// AddMessage at a time. On the consumer side of SourceCore, all consumption is protected by ValueLock, and thus
+ /// all consumption is serialized.
+ /// </remarks>
+ private readonly SingleProducerSingleConsumerQueue<TOutput> _messages = new SingleProducerSingleConsumerQueue<TOutput>(); // protected by AddMessage/ValueLock
+
+ /// <summary>Gets the object to use as the outgoing lock.</summary>
+ private object OutgoingLock { get { return _completionTask; } }
+ /// <summary>Gets the object to use as the value lock.</summary>
+ private object ValueLock { get { return _targetRegistry; } }
+
+ // *** These fields are readonly and are initialized by arguments to the constructor.
+
+ /// <summary>The source utilizing this helper.</summary>
+ private readonly ISourceBlock<TOutput> _owningSource;
+ /// <summary>The options used to configure this block's execution.</summary>
+ private readonly DataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>
+ /// An action to be invoked on the owner block to stop accepting messages.
+ /// This action is invoked when SourceCore encounters an exception.
+ /// </summary>
+ private readonly Action<ISourceBlock<TOutput>> _completeAction;
+ /// <summary>
+ /// An action to be invoked on the owner block when an item is removed.
+ /// This may be null if the owner block doesn't need to be notified.
+ /// </summary>
+ private readonly Action<ISourceBlock<TOutput>, int> _itemsRemovedAction;
+ /// <summary>Item counting function</summary>
+ private readonly Func<ISourceBlock<TOutput>, TOutput, IList<TOutput>, int> _itemCountingFunc;
+
+ // *** These fields are mutated during execution.
+
+ /// <summary>The task used to process the output and offer it to targets.</summary>
+ private Task _taskForOutputProcessing; // protected by ValueLock
+ /// <summary>Counter for message IDs unique within this source block.</summary>
+ private PaddedInt64 _nextMessageId = new PaddedInt64 { Value = 1 }; // We are going to use this value before incrementing. Protected by ValueLock.
+ /// <summary>The target that the next message is reserved for, or null if nothing is reserved.</summary>
+ private ITargetBlock<TOutput> _nextMessageReservedFor; // protected by OutgoingLock
+ /// <summary>Whether all future messages should be declined.</summary>
+ private bool _decliningPermanently; // Protected by ValueLock
+ /// <summary>Whether this block should again attempt to offer messages to targets.</summary>
+ private bool _enableOffering = true; // Protected by ValueLock, sometimes read with volatile reads
+ /// <summary>Whether someone has reserved the right to call CompleteBlockOncePossible.</summary>
+ private bool _completionReserved; // Protected by OutgoingLock
+ /// <summary>Exceptions that may have occurred and gone unhandled during processing.</summary>
+ private List<Exception> _exceptions; // Protected by ValueLock, sometimes read with volatile reads
+
+ /// <summary>Initializes the source core.</summary>
+ /// <param name="owningSource">The source utilizing this core.</param>
+ /// <param name="dataflowBlockOptions">The options to use to configure the block.</param>
+ /// <param name="completeAction">Action to invoke in order to decline the associated target half, which will in turn decline this source core.</param>
+ /// <param name="itemsRemovedAction">Action to invoke when one or more items is removed. This may be null.</param>
+ /// <param name="itemCountingFunc">
+ /// Action to invoke when the owner needs to be able to count the number of individual
+ /// items in an output or set of outputs.
+ /// </param>
+ internal SourceCore(
+ ISourceBlock<TOutput> owningSource, DataflowBlockOptions dataflowBlockOptions,
+ Action<ISourceBlock<TOutput>> completeAction,
+ Action<ISourceBlock<TOutput>, int> itemsRemovedAction = null,
+ Func<ISourceBlock<TOutput>, TOutput, IList<TOutput>, int> itemCountingFunc = null)
+ {
+ Contract.Requires(owningSource != null, "Core must be associated with a source.");
+ Contract.Requires(dataflowBlockOptions != null, "Options must be provided to configure the core.");
+ Contract.Requires(completeAction != null, "Action to invoke on completion is required.");
+
+ // Store the args
+ _owningSource = owningSource;
+ _dataflowBlockOptions = dataflowBlockOptions;
+ _itemsRemovedAction = itemsRemovedAction;
+ _itemCountingFunc = itemCountingFunc;
+ _completeAction = completeAction;
+
+ // Construct members that depend on the args
+ _targetRegistry = new TargetRegistry<TOutput>(_owningSource);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ internal IDisposable LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions)
+ {
+ // Validate arguments
+ if (target == null) throw new ArgumentNullException("target");
+ if (linkOptions == null) throw new ArgumentNullException("linkOptions");
+ Contract.EndContractBlock();
+
+ // If the block is already completed, there is not much to do -
+ // we have to propagate completion if that was requested, and
+ // then bail without taking the lock.
+ if (_completionTask.Task.IsCompleted)
+ {
+ if (linkOptions.PropagateCompletion) Common.PropagateCompletion(_completionTask.Task, target, exceptionHandler: null);
+ return Disposables.Nop;
+ }
+
+ lock (OutgoingLock)
+ {
+ // If completion has been reserved, the target registry has either been cleared already
+ // or is about to be cleared. So we can link and offer only if completion is not reserved.
+ if (!_completionReserved)
+ {
+ _targetRegistry.Add(ref target, linkOptions);
+ OfferToTargets(linkToTarget: target);
+ return Common.CreateUnlinker(OutgoingLock, _targetRegistry, target);
+ }
+ }
+
+ // The block should not offer any messages when it is in this state, but
+ // it should still propagate completion if that has been requested.
+ if (linkOptions.PropagateCompletion) Common.PropagateCompletionOnceCompleted(_completionTask.Task, target);
+ return Disposables.Nop;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ internal TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out Boolean messageConsumed)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ TOutput consumedMessageValue = default(TOutput);
+
+ lock (OutgoingLock)
+ {
+ // If this target doesn't hold the reservation, then for this ConsumeMessage
+ // to be valid, there must not be any reservation (since otherwise we can't
+ // consume a message destined for someone else).
+ if (_nextMessageReservedFor != target &&
+ _nextMessageReservedFor != null)
+ {
+ messageConsumed = false;
+ return default(TOutput);
+ }
+
+ lock (ValueLock)
+ {
+ // If the requested message isn't the next message to be served up, bail.
+ // Otherwise, we're good to go: dequeue the message as it will now be owned by the target,
+ // signal that we can resume enabling offering as there's potentially a new "next message",
+ // complete if necessary, and offer asynchronously all messages as is appropriate.
+
+ if (messageHeader.Id != _nextMessageId.Value ||
+ !_messages.TryDequeue(out consumedMessageValue))
+ {
+ messageConsumed = false;
+ return default(TOutput);
+ }
+
+ _nextMessageReservedFor = null;
+ _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true);
+ _enableOffering = true; // reenable offering if it was disabled
+ _nextMessageId.Value++;
+ CompleteBlockIfPossible();
+ OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true);
+ }
+ }
+
+ // Notify the owner block that our count has decreased
+ if (_itemsRemovedAction != null)
+ {
+ int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, consumedMessageValue, null) : 1;
+ _itemsRemovedAction(_owningSource, count);
+ }
+
+ // Return the consumed message value
+ messageConsumed = true;
+ return consumedMessageValue;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ internal Boolean ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ lock (OutgoingLock)
+ {
+ // If no one currently holds a reservation...
+ if (_nextMessageReservedFor == null)
+ {
+ lock (ValueLock)
+ {
+ // ...and if the requested message is next in the queue, allow it
+ if (messageHeader.Id == _nextMessageId.Value && !_messages.IsEmpty)
+ {
+ _nextMessageReservedFor = target;
+ _enableOffering = false;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ internal void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
+ {
+ // Validate arguments
+ if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ if (target == null) throw new ArgumentNullException("target");
+ Contract.EndContractBlock();
+
+ lock (OutgoingLock)
+ {
+ // If someone else holds the reservation, bail.
+ if (_nextMessageReservedFor != target) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+
+ lock (ValueLock)
+ {
+ // If this is not the message at the head of the queue, bail
+ if (messageHeader.Id != _nextMessageId.Value || _messages.IsEmpty) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
+
+ // Otherwise, release the reservation
+ _nextMessageReservedFor = null;
+ Debug.Assert(!_enableOffering, "Offering should have been disabled if there was a valid reservation");
+ _enableOffering = true;
+
+ // Now there is at least one message ready for offering. So offer it.
+ // If a cancellation is pending, this method will bail out.
+ OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true);
+
+ // This reservation may be holding the block's completion. So try to complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ internal Task Completion { get { return _completionTask.Task; } }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceive"]/*' />
+ internal Boolean TryReceive(Predicate<TOutput> filter, out TOutput item)
+ {
+ item = default(TOutput);
+ bool itemReceived = false;
+
+ lock (OutgoingLock)
+ {
+ // If the next message is reserved for someone, we can't receive right now. Otherwise...
+ if (_nextMessageReservedFor == null)
+ {
+ lock (ValueLock)
+ {
+ // If there's at least one message, and there's no filter or the next item
+ // passes the filter, dequeue it to be returned.
+ if (_messages.TryDequeueIf(filter, out item))
+ {
+ _nextMessageId.Value++;
+
+ // Now that the next message has changed, reenable offering if it was disabled
+ _enableOffering = true;
+
+ // If removing this item was the last thing this block will ever do, complete it,
+ CompleteBlockIfPossible();
+
+ // Now, try to offer up messages asynchronously, since we've
+ // changed what's at the head of the queue
+ OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true);
+
+ itemReceived = true;
+ }
+ }
+ }
+ }
+
+ if (itemReceived)
+ {
+ // Notify the owner block that our count has decreased
+ if (_itemsRemovedAction != null)
+ {
+ int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, item, null) : 1;
+ _itemsRemovedAction(_owningSource, count);
+ }
+ }
+ return itemReceived;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="TryReceiveAll"]/*' />
+ internal bool TryReceiveAll(out IList<TOutput> items)
+ {
+ items = null;
+ int countReceived = 0;
+
+ lock (OutgoingLock)
+ {
+ // If the next message is reserved for someone, we can't receive right now. Otherwise...
+ if (_nextMessageReservedFor == null)
+ {
+ lock (ValueLock)
+ {
+ if (!_messages.IsEmpty)
+ {
+ // Receive all of the data, clearing it out in the process.
+ var tmpList = new List<TOutput>();
+ TOutput item;
+ while (_messages.TryDequeue(out item)) tmpList.Add(item);
+ countReceived = tmpList.Count;
+ items = tmpList;
+
+ // Increment the next ID. Any new value is good.
+ _nextMessageId.Value++;
+
+ // Now that the next message has changed, reenable offering if it was disabled
+ _enableOffering = true;
+
+ // Now that the block is empty, check to see whether we should complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+ }
+
+ if (countReceived > 0)
+ {
+ // Notify the owner block that our count has decreased
+ if (_itemsRemovedAction != null)
+ {
+ int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, default(TOutput), items) : countReceived;
+ _itemsRemovedAction(_owningSource, count);
+ }
+ return true;
+ }
+ else return false;
+ }
+
+ /// <summary>Gets the number of items available to be received from this block.</summary>
+ internal int OutputCount { get { lock (OutgoingLock) lock (ValueLock) return _messages.Count; } }
+
+ /// <summary>
+ /// Adds a message to the source block for propagation.
+ /// This method must only be used by one thread at a time, and must not be used concurrently
+ /// with any other producer side methods, e.g. AddMessages, Complete.
+ /// </summary>
+ /// <param name="item">The item to be wrapped in a message to be added.</param>
+ internal void AddMessage(TOutput item)
+ {
+ // This method must not take the OutgoingLock, as it will likely be called in situations
+ // where an IncomingLock is held.
+
+ if (_decliningPermanently) return;
+ _messages.Enqueue(item);
+
+ Interlocked.MemoryBarrier(); // ensure the read of _taskForOutputProcessing doesn't move up before the writes in Enqueue
+
+ if (_taskForOutputProcessing == null)
+ {
+ // Separated out to enable inlining of AddMessage
+ OfferAsyncIfNecessaryWithValueLock();
+ }
+ }
+
+ /// <summary>
+ /// Adds messages to the source block for propagation.
+ /// This method must only be used by one thread at a time, and must not be used concurrently
+ /// with any other producer side methods, e.g. AddMessage, Complete.
+ /// </summary>
+ /// <param name="items">The list of items to be wrapped in messages to be added.</param>
+ internal void AddMessages(IEnumerable<TOutput> items)
+ {
+ Contract.Requires(items != null, "Items list must be valid.");
+
+ // This method must not take the OutgoingLock, as it will likely be called in situations
+ // where an IncomingLock is held.
+
+ if (_decliningPermanently) return;
+
+ // Special case arrays and lists, for which we can avoid the
+ // enumerator allocation that'll result from using a foreach.
+ // This also avoids virtual method calls that we'd get if we
+ // didn't special case.
+ var itemsAsList = items as List<TOutput>;
+ if (itemsAsList != null)
+ {
+ for (int i = 0; i < itemsAsList.Count; i++)
+ {
+ _messages.Enqueue(itemsAsList[i]);
+ }
+ }
+ else
+ {
+ TOutput[] itemsAsArray = items as TOutput[];
+ if (itemsAsArray != null)
+ {
+ for (int i = 0; i < itemsAsArray.Length; i++)
+ {
+ _messages.Enqueue(itemsAsArray[i]);
+ }
+ }
+ else
+ {
+ foreach (TOutput item in items)
+ {
+ _messages.Enqueue(item);
+ }
+ }
+ }
+
+ Interlocked.MemoryBarrier(); // ensure the read of _taskForOutputProcessing doesn't move up before the writes in Enqueue
+
+ if (_taskForOutputProcessing == null)
+ {
+ OfferAsyncIfNecessaryWithValueLock();
+ }
+ }
+
+ /// <summary>Adds an individual exceptionto this source.</summary>
+ /// <param name="exception">The exception to add</param>
+ internal void AddException(Exception exception)
+ {
+ Contract.Requires(exception != null, "Valid exception must be provided to be added.");
+ Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions.");
+ lock (ValueLock)
+ {
+ Common.AddException(ref _exceptions, exception);
+ }
+ }
+
+ /// <summary>Adds exceptions to this source.</summary>
+ /// <param name="exceptions">The exceptions to add</param>
+ internal void AddExceptions(List<Exception> exceptions)
+ {
+ Contract.Requires(exceptions != null, "Valid exceptions must be provided to be added.");
+ Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions.");
+ lock (ValueLock)
+ {
+ foreach (Exception exception in exceptions)
+ {
+ Common.AddException(ref _exceptions, exception);
+ }
+ }
+ }
+
+ /// <summary>Adds the exceptions contained in an AggregateException to this source.</summary>
+ /// <param name="aggregateException">The exception to add</param>
+ internal void AddAndUnwrapAggregateException(AggregateException aggregateException)
+ {
+ Contract.Requires(aggregateException != null && aggregateException.InnerExceptions.Count > 0, "Aggregate must be valid and contain inner exceptions to unwrap.");
+ Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions.");
+ lock (ValueLock)
+ {
+ Common.AddException(ref _exceptions, aggregateException, unwrapInnerExceptions: true);
+ }
+ }
+
+ /// <summary>Gets whether the _exceptions list is non-null.</summary>
+ internal bool HasExceptions
+ {
+ get
+ {
+ // We may check whether _exceptions is null without taking a lock because it is volatile
+ return Volatile.Read(ref _exceptions) != null;
+ }
+ }
+
+ /// <summary>Informs the block that it will not be receiving additional messages.</summary>
+ internal void Complete()
+ {
+ lock (ValueLock)
+ {
+ _decliningPermanently = true;
+
+ // CompleteAdding may be called in a context where an incoming lock is held. We need to
+ // call CompleteBlockIfPossible, but we can't do so if the incoming lock is held.
+ // However, we know that _decliningPermanently has been set, and thus the timing of
+ // CompleteBlockIfPossible doesn't matter, so we schedule it to run asynchronously
+ // and take the necessary locks in a situation where we're sure it won't cause a problem.
+ Task.Factory.StartNew(state =>
+ {
+ var thisSourceCore = (SourceCore<TOutput>)state;
+ lock (thisSourceCore.OutgoingLock)
+ {
+ lock (thisSourceCore.ValueLock)
+ {
+ thisSourceCore.CompleteBlockIfPossible();
+ }
+ }
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal DataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } }
+
+ /// <summary>Offers messages to all targets.</summary>
+ /// <param name="linkToTarget">
+ /// The newly linked target, if OfferToTargets is being called to synchronously
+ /// propagate to a target during a LinkTo operation.
+ /// </param>
+ private bool OfferToTargets(ITargetBlock<TOutput> linkToTarget = null)
+ {
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ // If the next message is reserved, we can't offer anything
+ if (_nextMessageReservedFor != null)
+ return false;
+
+ // Peek at the next message if there is one, so we can offer it.
+ DataflowMessageHeader header = default(DataflowMessageHeader);
+ TOutput message = default(TOutput);
+ bool offerJustToLinkToTarget = false;
+
+ // If offering isn't enabled and if we're not doing this as
+ // a result of LinkTo, bail. Otherwise, with offering disabled, we must have
+ // already offered this message to all existing targets, so we can just offer
+ // it to the newly linked target.
+ if (!Volatile.Read(ref _enableOffering))
+ {
+ if (linkToTarget == null) return false;
+ else offerJustToLinkToTarget = true;
+ }
+
+ // Otherwise, peek at message to offer
+ if (_messages.TryPeek(out message))
+ {
+ header = new DataflowMessageHeader(_nextMessageId.Value);
+ }
+
+ // If there is a message, offer it.
+ bool messageWasAccepted = false;
+ if (header.IsValid)
+ {
+ if (offerJustToLinkToTarget)
+ {
+ // If we've already offered the message to everyone else,
+ // we can just offer it to the newly linked target
+ Debug.Assert(linkToTarget != null, "Must have a valid target to offer to.");
+ OfferMessageToTarget(header, message, linkToTarget, out messageWasAccepted);
+ }
+ else
+ {
+ // Otherwise, we've not yet offered this message to anyone, so even
+ // if linkToTarget is non-null, we need to propagate the message in order
+ // through all of the registered targets, the last of which will be the linkToTarget
+ // if it's non-null (no need to special-case it, though).
+
+ // Note that during OfferMessageToTarget, a target may call ConsumeMessage (taking advantage of the
+ // reentrancy of OutgoingLock), which may unlink the target if the target is registered as "unlinkAfterOne".
+ // Doing so will remove the target from the targets list. As such, we maintain the next node
+ // separately from cur.Next, in case cur.Next changes by cur being removed from the list.
+ // No other node in the list should change, as we're protected by OutgoingLock.
+
+ TargetRegistry<TOutput>.LinkedTargetInfo cur = _targetRegistry.FirstTargetNode;
+ while (cur != null)
+ {
+ TargetRegistry<TOutput>.LinkedTargetInfo next = cur.Next;
+ if (OfferMessageToTarget(header, message, cur.Target, out messageWasAccepted)) break;
+ cur = next;
+ }
+
+ // If none of the targets accepted the message, disable offering.
+ if (!messageWasAccepted)
+ {
+ lock (ValueLock)
+ {
+ _enableOffering = false;
+ }
+ }
+ }
+ }
+
+ // If a message got accepted, consume it and reenable offering.
+ if (messageWasAccepted)
+ {
+ lock (ValueLock)
+ {
+ // SourceCore set consumeToAccept to false. However, it's possible
+ // that an incorrectly written target may ignore that parameter and synchronously consume
+ // even though they weren't supposed to. To recover from that,
+ // we'll only dequeue if the correct message is still at the head of the queue.
+ // However, we'll assert so that we can at least catch this in our own debug builds.
+ TOutput dropped;
+ if (_nextMessageId.Value != header.Id ||
+ !_messages.TryDequeue(out dropped)) // remove the next message
+ {
+ Debug.Assert(false, "The target did not follow the protocol.");
+ }
+ _nextMessageId.Value++;
+
+ // The message was accepted, so there's now going to be a new next message.
+ // If offering had been disabled, reenable it.
+ _enableOffering = true;
+
+ // Now that a message has been removed, we need to complete if possible or
+ // or asynchronously offer if necessary. However, if we're calling this as part of our
+ // offering loop, we won't be able to do either, since by definition there's already
+ // a processing task spun up (us) that would prevent these things. So we only
+ // do the checks if we're being called to link a new target rather than as part
+ // of normal processing.
+ if (linkToTarget != null)
+ {
+ CompleteBlockIfPossible();
+ OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true);
+ }
+ }
+
+ // Notify the owner block that our count has decreased
+ if (_itemsRemovedAction != null)
+ {
+ int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, message, null) : 1;
+ _itemsRemovedAction(_owningSource, count);
+ }
+ }
+
+ return messageWasAccepted;
+ }
+
+ /// <summary>Offers the message to the target.</summary>
+ /// <param name="header">The header of the message to offer.</param>
+ /// <param name="message">The message being offered.</param>
+ /// <param name="target">The single target to which the message should be offered.</param>
+ /// <param name="messageWasAccepted">true if the message was accepted by the target; otherwise, false.</param>
+ /// <returns>
+ /// true if the message should not be offered to additional targets;
+ /// false if propagation should be allowed to continue.
+ /// </returns>
+ private bool OfferMessageToTarget(
+ DataflowMessageHeader header, TOutput message, ITargetBlock<TOutput> target,
+ out bool messageWasAccepted)
+ {
+ Contract.Requires(target != null, "Valid target to offer to is required.");
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: false);
+
+ DataflowMessageStatus result = target.OfferMessage(header, message, _owningSource, consumeToAccept: false);
+ Debug.Assert(result != DataflowMessageStatus.NotAvailable, "Messages are not being offered concurrently, so nothing should be missed.");
+ messageWasAccepted = false;
+
+ // If accepted, note it, and if the target was linked as "once", remove it
+ if (result == DataflowMessageStatus.Accepted)
+ {
+ _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true);
+ messageWasAccepted = true;
+ return true; // the message should not be offered to anyone else
+ }
+ // If declined permanently, remove the target
+ else if (result == DataflowMessageStatus.DecliningPermanently)
+ {
+ _targetRegistry.Remove(target);
+ }
+ // If the message was reserved by the target, stop propagating
+ else if (_nextMessageReservedFor != null)
+ {
+ Debug.Assert(result == DataflowMessageStatus.Postponed,
+ "If the message was reserved, it should also have been postponed.");
+ return true; // the message should not be offered to anyone else
+ }
+ // If the result was Declined, there's nothing more to be done.
+ // This message will sit at the front of the queue until someone claims it.
+
+ return false; // allow the message to be offered to someone else
+ }
+
+ /// <summary>
+ /// Called when we want to enable asynchronously offering message to targets.
+ /// Takes the ValueLock before delegating to OfferAsyncIfNecessary.
+ /// </summary>
+ private void OfferAsyncIfNecessaryWithValueLock()
+ {
+ lock (ValueLock)
+ {
+ OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: false);
+ }
+ }
+
+ /// <summary>Called when we want to enable asynchronously offering message to targets.</summary>
+ /// <param name="isReplacementReplica">Whether this call is the continuation of a previous message loop.</param>
+ /// <param name="outgoingLockKnownAcquired">Whether the caller is sure that the outgoing lock is currently held by this thread.</param>
+ private void OfferAsyncIfNecessary(bool isReplacementReplica, bool outgoingLockKnownAcquired)
+ {
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+
+ // Fast path to enable OfferAsyncIfNecessary to be inlined. We only need
+ // to proceed if there's no task processing, offering is enabled, and
+ // there are no messages to be processed.
+ if (_taskForOutputProcessing == null && _enableOffering && !_messages.IsEmpty)
+ {
+ // Slow path: do additional checks and potentially launch new task
+ OfferAsyncIfNecessary_Slow(isReplacementReplica, outgoingLockKnownAcquired);
+ }
+ }
+
+ /// <summary>Called when we want to enable asynchronously offering message to targets.</summary>
+ /// <param name="isReplacementReplica">Whether this call is the continuation of a previous message loop.</param>
+ /// <param name="outgoingLockKnownAcquired">Whether the caller is sure that the outgoing lock is currently held by this thread.</param>
+ private void OfferAsyncIfNecessary_Slow(bool isReplacementReplica, bool outgoingLockKnownAcquired)
+ {
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+ Debug.Assert(_taskForOutputProcessing == null && _enableOffering && !_messages.IsEmpty,
+ "The block must be enabled for offering, not currently be processing, and have messages available to process.");
+
+ // This method must not take the outgoing lock, as it will likely be called in situations
+ // where a derived type's incoming lock is held.
+
+ bool targetsAvailable = true;
+ if (outgoingLockKnownAcquired || Monitor.IsEntered(OutgoingLock))
+ {
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ targetsAvailable = _targetRegistry.FirstTargetNode != null;
+ }
+
+ // If there's any work to be done...
+ if (targetsAvailable && !CanceledOrFaulted)
+ {
+ // Create task and store into _taskForOutputProcessing prior to scheduling the task
+ // so that _taskForOutputProcessing will be visibly set in the task loop.
+ _taskForOutputProcessing = new Task(thisSourceCore => ((SourceCore<TOutput>)thisSourceCore).OfferMessagesLoopCore(), this,
+ Common.GetCreationOptionsForTask(isReplacementReplica));
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ _owningSource, _taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _messages.Count);
+ }
+#endif
+
+ // Start the task handling scheduling exceptions
+#pragma warning disable 0420
+ Exception exception = Common.StartTaskSafe(_taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler);
+#pragma warning restore 0420
+ if (exception != null)
+ {
+ // First, log the exception while the processing state is dirty which is preventing the block from completing.
+ // Then revert the proactive processing state changes.
+ // And last, try to complete the block.
+ AddException(exception);
+ _taskForOutputProcessing = null;
+ _decliningPermanently = true;
+
+ // Get out from under currently held locks - ValueLock is taken, but OutgoingLock may not be.
+ // Re-take the locks on a separate thread.
+ Task.Factory.StartNew(state =>
+ {
+ var thisSourceCore = (SourceCore<TOutput>)state;
+ lock (thisSourceCore.OutgoingLock)
+ {
+ lock (thisSourceCore.ValueLock)
+ {
+ thisSourceCore.CompleteBlockIfPossible();
+ }
+ }
+ }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ if (exception != null) AddException(exception);
+ }
+ }
+
+ /// <summary>Task body used to process messages.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void OfferMessagesLoopCore()
+ {
+ Debug.Assert(_taskForOutputProcessing != null && _taskForOutputProcessing.Id == Task.CurrentId,
+ "Must be part of the current processing task.");
+ try
+ {
+ int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask;
+
+ // We need to hold the outgoing lock while offering messages. We can either
+ // lock and unlock for each individual offering, or we can lock around multiple or all
+ // possible offerings. The former ensures that other operations don't get starved,
+ // while the latter is much more efficient (not continually acquiring and releasing
+ // the lock). For blocks that aren't linked to any targets, this won't matter
+ // (no offering is done), and for blocks that are only linked to targets, this shouldn't
+ // matter (no one is contending for the lock), thus
+ // the only case it would matter is when a block both has targets and is being
+ // explicitly received from, which is an uncommon scenario. Thus, we want to lock
+ // around the whole thing to improve performance, but just in case we do hit
+ // an uncommon scenario, in the default case we release the lock every now and again.
+ // If a developer wants to control this, they can limit the duration of the
+ // lock by using MaxMessagesPerTask.
+
+ const int DEFAULT_RELEASE_LOCK_ITERATIONS = 10; // Dialable
+ int releaseLockIterations =
+ _dataflowBlockOptions.MaxMessagesPerTask == DataflowBlockOptions.Unbounded ?
+ DEFAULT_RELEASE_LOCK_ITERATIONS : maxMessagesPerTask;
+
+ for (int messageCounter = 0;
+ messageCounter < maxMessagesPerTask && !CanceledOrFaulted;)
+ {
+ lock (OutgoingLock)
+ {
+ // While there are more messages to process, offer each in turn
+ // to the targets. If we're unable to propagate a particular message,
+ // stop trying until something changes in the future.
+ for (
+ int lockReleaseCounter = 0;
+ messageCounter < maxMessagesPerTask && lockReleaseCounter < releaseLockIterations && !CanceledOrFaulted;
+ ++messageCounter, ++lockReleaseCounter)
+ {
+ if (!OfferToTargets()) return;
+ }
+ }
+ }
+ }
+ catch (Exception exc)
+ {
+ // Record the exception
+ AddException(exc);
+
+ // Notify the owning block it should stop accepting new messages
+ _completeAction(_owningSource);
+ }
+ finally
+ {
+ lock (OutgoingLock)
+ {
+ lock (ValueLock)
+ {
+ // We're no longer processing, so null out the processing task
+ Debug.Assert(_taskForOutputProcessing != null && _taskForOutputProcessing.Id == Task.CurrentId,
+ "Must be part of the current processing task.");
+ _taskForOutputProcessing = null;
+ Interlocked.MemoryBarrier(); // synchronize with AddMessage(s) and its read of _taskForOutputProcessing
+
+ // 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.
+ OfferAsyncIfNecessary(isReplacementReplica: true, outgoingLockKnownAcquired: true);
+
+ // If, however, we stopped because we ran out of work to do and we
+ // know we'll never get more, then complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+ }
+
+ /// <summary>Gets whether the source has had cancellation requested or an exception has occurred.</summary>
+ private bool CanceledOrFaulted
+ {
+ get
+ {
+ // Cancellation is honored as soon as the CancellationToken has been signaled.
+ // Faulting is honored after an exception has been encountered and the owning block
+ // has invoked Complete on us.
+ return _dataflowBlockOptions.CancellationToken.IsCancellationRequested ||
+ (HasExceptions && _decliningPermanently);
+ }
+ }
+
+ /// <summary>Completes the block's processing if there's nothing left to do and never will be.</summary>
+ private void CompleteBlockIfPossible()
+ {
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+
+ if (!_completionReserved)
+ {
+ if (_decliningPermanently && // declining permanently, so no more messages will arrive
+ _taskForOutputProcessing == null && // no current processing
+ _nextMessageReservedFor == null) // no pending reservation
+ {
+ CompleteBlockIfPossible_Slow();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Slow path for CompleteBlockIfPossible.
+ /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined.
+ /// </summary>
+ private void CompleteBlockIfPossible_Slow()
+ {
+ Contract.Requires(
+ _decliningPermanently && _taskForOutputProcessing == null && _nextMessageReservedFor == null,
+ "The block must be declining permanently, there must be no reservations, and there must be no processing tasks");
+ Common.ContractAssertMonitorStatus(OutgoingLock, held: true);
+ Common.ContractAssertMonitorStatus(ValueLock, held: true);
+
+ if (_messages.IsEmpty || CanceledOrFaulted)
+ {
+ _completionReserved = true;
+
+ // Get out from under currently held locks. This is to avoid
+ // invoking synchronous continuations off of _completionTask.Task
+ // while holding a lock.
+ Task.Factory.StartNew(state => ((SourceCore<TOutput>)state).CompleteBlockOncePossible(),
+ this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>
+ /// Completes the block. This must only be called once, and only once all of the completion conditions are met.
+ /// As such, it must only be called from CompleteBlockIfPossible.
+ /// </summary>
+ private void CompleteBlockOncePossible()
+ {
+ TargetRegistry<TOutput>.LinkedTargetInfo linkedTargets;
+ List<Exception> exceptions;
+
+ // Avoid completing while the code that caused this completion to occur is still holding a lock.
+ // Clear out the target registry and buffers to help avoid memory leaks.
+ lock (OutgoingLock)
+ {
+ // Save the linked list of targets so that it could be traversed later to propagate completion
+ linkedTargets = _targetRegistry.ClearEntryPoints();
+ lock (ValueLock)
+ {
+ _messages.Clear();
+
+ // Save a local reference to the exceptions list and null out the field,
+ // so that if the target side tries to add an exception this late,
+ // it will go to a separate list (that will be ignored.)
+ exceptions = _exceptions;
+ _exceptions = null;
+ }
+ }
+
+ // If it's due to an unhandled exception, finish in an error state
+ if (exceptions != null)
+ {
+ _completionTask.TrySetException(exceptions);
+ }
+ // If it's due to cancellation, finish in a canceled state
+ else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ {
+ _completionTask.TrySetCanceled();
+ }
+ // Otherwise, finish in a successful state.
+ else
+ {
+ _completionTask.TrySetResult(default(VoidResult));
+ }
+
+ // Now that the completion task is completed, we may propagate completion to the linked targets
+ _targetRegistry.PropagateCompletion(linkedTargets);
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCompleted(_owningSource);
+ }
+#endif
+ }
+
+ /// <summary>Gets the object to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displaySource = _owningSource as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displaySource != null ? displaySource.Content : _owningSource);
+ }
+ }
+
+ /// <summary>Gets information about this helper to be used for display in a debugger.</summary>
+ /// <returns>Debugging information about this source core.</returns>
+ internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); }
+
+ /// <summary>Provides debugging information about the source core.</summary>
+ internal sealed class DebuggingInformation
+ {
+ /// <summary>The source being viewed.</summary>
+ private SourceCore<TOutput> _source;
+
+ /// <summary>Initializes the type proxy.</summary>
+ /// <param name="source">The source being viewed.</param>
+ internal DebuggingInformation(SourceCore<TOutput> source) { _source = source; }
+
+ /// <summary>Gets the number of messages available for receiving.</summary>
+ internal int OutputCount { get { return _source._messages.Count; } }
+ /// <summary>Gets the messages available for receiving.</summary>
+ internal IEnumerable<TOutput> OutputQueue { get { return _source._messages.ToList(); } }
+ /// <summary>Gets the task being used for output processing.</summary>
+ internal Task TaskForOutputProcessing { get { return _source._taskForOutputProcessing; } }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal DataflowBlockOptions DataflowBlockOptions { get { return _source._dataflowBlockOptions; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ internal bool IsDecliningPermanently { get { return _source._decliningPermanently; } }
+ /// <summary>Gets whether the block is completed.</summary>
+ internal bool IsCompleted { get { return _source.Completion.IsCompleted; } }
+
+ /// <summary>Gets the set of all targets linked from this block.</summary>
+ internal TargetRegistry<TOutput> LinkedTargets { get { return _source._targetRegistry; } }
+ /// <summary>Gets the target that holds a reservation on the next message, if any.</summary>
+ internal ITargetBlock<TOutput> NextMessageReservedFor { get { return _source._nextMessageReservedFor; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// SpscTargetCore.cs
+//
+//
+// A fast single-producer-single-consumer core for a target block.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Security;
+
+#pragma warning disable 0420 // turn off warning for passing volatiles to interlocked operations
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ // SpscTargetCore provides a fast target core for use in blocks that will only have single-producer-single-consumer
+ // semantics. Blocks configured with the default DOP==1 will be single consumer, so whether this core may be
+ // used is largely up to whether the block is also single-producer. The ExecutionDataflowBlockOptions.SingleProducerConstrained
+ // option can be used by a developer to inform a block that it will only be accessed by one producer at a time,
+ // and a block like ActionBlock can utilize that knowledge to choose this target instead of the default TargetCore.
+ // However, there are further constraints that might prevent this core from being used.
+ // - If the user specifies a CancellationToken, this core can't be used, as the cancellation request
+ // could come in concurrently with the single producer accessing the block, thus resulting in multiple producers.
+ // - If the user specifies a bounding capacity, this core can't be used, as the consumer processing items
+ // needs to synchronize with producers around the change in bounding count, and the consumer is again
+ // in effect another producer.
+ // - If the block has a source half (e.g. TransformBlock) and that source could potentially call back
+ // to the target half to, for example, notify it of exceptions occurring, again there would potentially
+ // be multiple producers.
+ // Thus, when and how this SpscTargetCore may be applied is significantly constrained.
+
+ /// <summary>
+ /// Provides a core implementation of <see cref="ITargetBlock{TInput}"/> for use when there's only a single producer posting data.
+ /// </summary>
+ /// <typeparam name="TInput">Specifies the type of data accepted by the <see cref="TargetCore{TInput}"/>.</typeparam>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ internal sealed class SpscTargetCore<TInput>
+ {
+ /// <summary>The target block using this helper.</summary>
+ private readonly ITargetBlock<TInput> _owningTarget;
+ /// <summary>The messages in this target.</summary>
+ private readonly SingleProducerSingleConsumerQueue<TInput> _messages = new SingleProducerSingleConsumerQueue<TInput>();
+ /// <summary>The options to use to configure this block. The target core assumes these options are immutable.</summary>
+ private readonly ExecutionDataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>An action to invoke for every accepted message.</summary>
+ private readonly Action<TInput> _action;
+
+ /// <summary>Exceptions that may have occurred and gone unhandled during processing. This field is lazily initialized.</summary>
+ private volatile List<Exception> _exceptions;
+ /// <summary>Whether to stop accepting new messages.</summary>
+ private volatile bool _decliningPermanently;
+ /// <summary>A task has reserved the right to run the completion routine.</summary>
+ private volatile bool _completionReserved;
+ /// <summary>
+ /// The Task currently active to process the block. This field is used to synchronize between producer and consumer,
+ /// and it should not be set to null once the block completes, as doing so would allow for races where the producer
+ /// gets another consumer task queued even though the block has completed.
+ /// </summary>
+ private volatile Task _activeConsumer;
+ /// <summary>A task representing the completion of the block. This field is lazily initialized.</summary>
+ private TaskCompletionSource<VoidResult> _completionTask;
+
+ /// <summary>Initialize the SPSC target core.</summary>
+ /// <param name="owningTarget">The owning target block.</param>
+ /// <param name="action">The action to be invoked for every message.</param>
+ /// <param name="dataflowBlockOptions">The options to use to configure this block. The target core assumes these options are immutable.</param>
+ internal SpscTargetCore(
+ ITargetBlock<TInput> owningTarget, Action<TInput> action, ExecutionDataflowBlockOptions dataflowBlockOptions)
+ {
+ Contract.Requires(owningTarget != null, "Expected non-null owningTarget");
+ Contract.Requires(action != null, "Expected non-null action");
+ Contract.Requires(dataflowBlockOptions != null, "Expected non-null dataflowBlockOptions");
+
+ _owningTarget = owningTarget;
+ _action = action;
+ _dataflowBlockOptions = dataflowBlockOptions;
+ }
+
+ internal bool Post(TInput messageValue)
+ {
+ if (_decliningPermanently)
+ return false;
+
+ // Store the offered message into the queue.
+ _messages.Enqueue(messageValue);
+
+ Interlocked.MemoryBarrier(); // ensure the read of _activeConsumer doesn't move up before the writes in Enqueue
+
+ // Make sure there's an active task available to handle processing this message. If we find the task
+ // is null, we'll try to schedule one using an interlocked operation. If we find the task is non-null,
+ // then there must be a task actively running. If there's a race where the task is about to complete
+ // and nulls out its reference (using a barrier), it'll subsequently check whether there are any messages in the queue,
+ // and since we put the messages into the queue before now, it'll find them and use an interlocked
+ // to re-launch itself.
+ if (_activeConsumer == null)
+ {
+ ScheduleConsumerIfNecessary(false);
+ }
+
+ return true;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ internal DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, bool consumeToAccept)
+ {
+ // If we're not required to go back to the source to consume the offered message, try fast path.
+ return !consumeToAccept && Post(messageValue) ?
+ DataflowMessageStatus.Accepted :
+ OfferMessage_Slow(messageHeader, messageValue, source, consumeToAccept);
+ }
+
+ /// <summary>Implements the slow path for OfferMessage.</summary>
+ /// <param name="messageHeader">The message header for the offered value.</param>
+ /// <param name="messageValue">The offered value.</param>
+ /// <param name="source">The source offering the message. This may be null.</param>
+ /// <param name="consumeToAccept">true if we need to call back to the source to consume the message; otherwise, false if we can simply accept it directly.</param>
+ /// <returns>The status of the message.</returns>
+ private DataflowMessageStatus OfferMessage_Slow(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, bool consumeToAccept)
+ {
+ // If we're declining permanently, let the caller know.
+ if (_decliningPermanently)
+ {
+ return DataflowMessageStatus.DecliningPermanently;
+ }
+
+ // If the message header is invalid, throw.
+ if (!messageHeader.IsValid)
+ {
+ throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
+ }
+
+ // If the caller has requested we consume the message using ConsumeMessage, do so.
+ if (consumeToAccept)
+ {
+ if (source == null) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept");
+ bool consumed;
+ messageValue = source.ConsumeMessage(messageHeader, _owningTarget, out consumed);
+ if (!consumed) return DataflowMessageStatus.NotAvailable;
+ }
+
+ // See the "fast path" comments in Post
+ _messages.Enqueue(messageValue);
+ Interlocked.MemoryBarrier(); // ensure the read of _activeConsumer doesn't move up before the writes in Enqueue
+ if (_activeConsumer == null)
+ {
+ ScheduleConsumerIfNecessary(isReplica: false);
+ }
+ return DataflowMessageStatus.Accepted;
+ }
+
+ /// <summary>Schedules a consumer task if there's none currently running.</summary>
+ /// <param name="isReplica">Whether the new consumer is being scheduled to replace a currently running consumer.</param>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ private void ScheduleConsumerIfNecessary(bool isReplica)
+ {
+ // If there's currently no active task...
+ if (_activeConsumer == null)
+ {
+ // Create a new consumption task and try to set it as current as long as there's still no other task
+ var newConsumer = new Task(
+ state => ((SpscTargetCore<TInput>)state).ProcessMessagesLoopCore(),
+ this, CancellationToken.None, Common.GetCreationOptionsForTask(isReplica));
+ if (Interlocked.CompareExchange(ref _activeConsumer, newConsumer, null) == null)
+ {
+ // We won the race. This task is now the consumer.
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ _owningTarget, newConsumer, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, _messages.Count);
+ }
+#endif
+
+ // Start the task. In the erroneous case where the scheduler throws an exception,
+ // just allow it to propagate. Our other option would be to fault the block with
+ // that exception, but in order for the block to complete we need to schedule a consumer
+ // task to do so, and it's very likely that if the scheduler is throwing an exception
+ // now, it would do so again.
+ newConsumer.Start(_dataflowBlockOptions.TaskScheduler);
+ }
+ }
+ }
+
+ /// <summary>Task body used to process messages.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void ProcessMessagesLoopCore()
+ {
+ Debug.Assert(
+ _activeConsumer != null && _activeConsumer.Id == Task.CurrentId,
+ "This method should only be called when it's the active consumer.");
+
+ int messagesProcessed = 0;
+ int maxMessagesToProcess = _dataflowBlockOptions.ActualMaxMessagesPerTask;
+
+ // Continue processing as long as there's more processing to be done
+ bool continueProcessing = true;
+ while (continueProcessing)
+ {
+ continueProcessing = false;
+ TInput nextMessage = default(TInput);
+ try
+ {
+ // While there are more messages to be processed, process each one.
+ // NOTE: This loop is critical for performance. It must be super lean.
+ while (
+ _exceptions == null &&
+ messagesProcessed < maxMessagesToProcess &&
+ _messages.TryDequeue(out nextMessage))
+ {
+ messagesProcessed++; // done before _action invoked in case it throws exception
+ _action(nextMessage);
+ }
+ }
+ catch (Exception exc)
+ {
+ // If the exception is for cancellation, just ignore it.
+ // Otherwise, store it, and the finally block will handle completion.
+ if (!Common.IsCooperativeCancellation(exc))
+ {
+ _decliningPermanently = true; // stop accepting from producers
+ Common.StoreDataflowMessageValueIntoExceptionData<TInput>(exc, nextMessage, false);
+ StoreException(exc);
+ }
+ }
+ finally
+ {
+ // If more messages just arrived and we should still process them,
+ // loop back around and keep going.
+ if (!_messages.IsEmpty && _exceptions == null && (messagesProcessed < maxMessagesToProcess))
+ {
+ continueProcessing = true;
+ }
+ else
+ {
+ // If messages are being declined and we're empty, or if there's an exception,
+ // then there's no more work to be done and we should complete the block.
+ bool wasDecliningPermanently = _decliningPermanently;
+ if ((wasDecliningPermanently && _messages.IsEmpty) || _exceptions != null)
+ {
+ // Complete the block, as long as we're not already completing or completed.
+ if (!_completionReserved) // no synchronization necessary; this can't happen concurrently
+ {
+ _completionReserved = true;
+ CompleteBlockOncePossible();
+ }
+ }
+ else
+ {
+ // Mark that we're exiting.
+ Task previousConsumer = Interlocked.Exchange(ref _activeConsumer, null);
+ Debug.Assert(previousConsumer != null && previousConsumer.Id == Task.CurrentId,
+ "The running task should have been denoted as the active task.");
+
+ // Now that we're no longer the active task, double
+ // check to make sure there's really nothing to do,
+ // which could include processing more messages or completing.
+ // If there is more to do, schedule a task to try to do it.
+ // This is to handle a race with Post/Complete/Fault and this
+ // task completing.
+ if (!_messages.IsEmpty || // messages to be processed
+ (!wasDecliningPermanently && _decliningPermanently) || // potentially completion to be processed
+ _exceptions != null) // exceptions/completion to be processed
+ {
+ ScheduleConsumerIfNecessary(isReplica: true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>Gets the number of messages waiting to be processed.</summary>
+ internal int InputCount { get { return _messages.Count; } }
+
+ /// <summary>
+ /// Completes the target core. If an exception is provided, the block will end up in a faulted state.
+ /// If Complete is invoked more than once, or if it's invoked after the block is already
+ /// completing, all invocations after the first are ignored.
+ /// </summary>
+ /// <param name="exception">The exception to be stored.</param>
+ internal void Complete(Exception exception)
+ {
+ // If we're not yet declining permanently...
+ if (!_decliningPermanently)
+ {
+ // Mark us as declining permanently, and then kick off a processing task
+ // if we need one. It's this processing task's job to complete the block
+ // once all data has been consumed and/or we're in a valid state for completion.
+ if (exception != null) StoreException(exception);
+ _decliningPermanently = true;
+ ScheduleConsumerIfNecessary(isReplica: false);
+ }
+ }
+
+ /// <summary>
+ /// Ensures the exceptions list is initialized and stores the exception into the list using a lock.
+ /// </summary>
+ /// <param name="exception">The exception to store.</param>
+ private void StoreException(Exception exception)
+ {
+ // Ensure that the _exceptions field has been initialized.
+ // We need to synchronize the initialization and storing of
+ // the exception because this method could be accessed concurrently
+ // by the producer and consumer, a producer calling Fault and the
+ // processing task processing the user delegate which might throw.
+ lock (LazyInitializer.EnsureInitialized(ref _exceptions, () => new List<Exception>()))
+ {
+ _exceptions.Add(exception);
+ }
+ }
+
+ /// <summary>
+ /// Completes the block. This must only be called once, and only once all of the completion conditions are met.
+ /// </summary>
+ private void CompleteBlockOncePossible()
+ {
+ Debug.Assert(_completionReserved, "Should only invoke once completion has been reserved.");
+
+ // Dump any messages that might remain in the queue, which could happen if we completed due to exceptions.
+ TInput dumpedMessage;
+ while (_messages.TryDequeue(out dumpedMessage)) ;
+
+ // Complete the completion task
+ bool result;
+ if (_exceptions != null)
+ {
+ Exception[] exceptions;
+ lock (_exceptions) exceptions = _exceptions.ToArray();
+ result = CompletionSource.TrySetException(exceptions);
+ }
+ else
+ {
+ result = CompletionSource.TrySetResult(default(VoidResult));
+ }
+ Debug.Assert(result, "Expected completion task to not yet be completed");
+ // We explicitly do not set the _activeTask to null here, as that would
+ // allow for races where a producer calling OfferMessage could end up
+ // seeing _activeTask as null and queueing a new consumer task even
+ // though the block has completed.
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockCompleted(_owningTarget);
+ }
+#endif
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ internal Task Completion { get { return CompletionSource.Task; } }
+
+ /// <summary>Gets the lazily-initialized completion source.</summary>
+ private TaskCompletionSource<VoidResult> CompletionSource
+ {
+ get { return LazyInitializer.EnsureInitialized(ref _completionTask, () => new TaskCompletionSource<VoidResult>()); }
+ }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } }
+
+ /// <summary>Gets information about this helper to be used for display in a debugger.</summary>
+ /// <returns>Debugging information about this target.</returns>
+ internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); }
+
+ /// <summary>Gets the object to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayTarget = _owningTarget as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displayTarget != null ? displayTarget.Content : _owningTarget);
+ }
+ }
+
+ /// <summary>Provides a wrapper for commonly needed debugging information.</summary>
+ internal sealed class DebuggingInformation
+ {
+ /// <summary>The target being viewed.</summary>
+ private readonly SpscTargetCore<TInput> _target;
+
+ /// <summary>Initializes the debugging helper.</summary>
+ /// <param name="target">The target being viewed.</param>
+ internal DebuggingInformation(SpscTargetCore<TInput> target) { _target = target; }
+
+ /// <summary>Gets the number of messages waiting to be processed.</summary>
+ internal int InputCount { get { return _target.InputCount; } }
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ internal IEnumerable<TInput> InputQueue { get { return _target._messages.ToList(); } }
+
+ /// <summary>Gets the current number of outstanding input processing operations.</summary>
+ internal Int32 CurrentDegreeOfParallelism { get { return _target._activeConsumer != null && !_target.Completion.IsCompleted ? 1 : 0; } }
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ internal bool IsDecliningPermanently { get { return _target._decliningPermanently; } }
+ /// <summary>Gets whether the block is completed.</summary>
+ internal bool IsCompleted { get { return _target.Completion.IsCompleted; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// TargetCore.cs
+//
+//
+// The core implementation of a standard ITargetBlock<TInput>.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Security;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ // LOCK-LEVELING SCHEME
+ // --------------------
+ // TargetCore employs a single lock: IncomingLock. This lock must not be used when calling out to any targets,
+ // which TargetCore should not have, anyway. It also must not be held when calling back to any sources, except
+ // during calls to OfferMessage from that same source.
+
+ /// <summary>Options used to configure a target core.</summary>
+ [Flags]
+ internal enum TargetCoreOptions : byte
+ {
+ /// <summary>Synchronous completion, both a target and a source, etc.</summary>
+ None = 0x0,
+ /// <summary>Whether the block relies on the delegate to signal when an async operation has completed.</summary>
+ UsesAsyncCompletion = 0x1,
+ /// <summary>
+ /// Whether the block containing this target core is just a target or also has a source side.
+ /// If it's just a target, then this target core's completion represents the entire block's completion.
+ /// </summary>
+ RepresentsBlockCompletion = 0x2
+ }
+
+ /// <summary>
+ /// Provides a core implementation of <see cref="ITargetBlock{TInput}"/>.</summary>
+ /// <typeparam name="TInput">Specifies the type of data accepted by the <see cref="TargetCore{TInput}"/>.</typeparam>
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ internal sealed class TargetCore<TInput>
+ {
+ // *** These fields are readonly and are initialized at AppDomain startup.
+
+ /// <summary>Caching the keep alive predicate.</summary>
+ private static readonly Common.KeepAlivePredicate<TargetCore<TInput>, KeyValuePair<TInput, long>> _keepAlivePredicate =
+ (TargetCore<TInput> thisTargetCore, out KeyValuePair<TInput, long> messageWithId) =>
+ thisTargetCore.TryGetNextAvailableOrPostponedMessage(out messageWithId);
+
+ // *** These fields are readonly and are initialized to new instances at construction.
+
+ /// <summary>A task representing the completion of the block.</summary>
+ private readonly TaskCompletionSource<VoidResult> _completionSource = new TaskCompletionSource<VoidResult>();
+
+ // *** These fields are readonly and are initialized by arguments to the constructor.
+
+ /// <summary>The target block using this helper.</summary>
+ private readonly ITargetBlock<TInput> _owningTarget;
+ /// <summary>The messages in this target.</summary>
+ /// <remarks>This field doubles as the IncomingLock.</remarks>
+ private readonly IProducerConsumerQueue<KeyValuePair<TInput, long>> _messages;
+ /// <summary>The options associated with this block.</summary>
+ private readonly ExecutionDataflowBlockOptions _dataflowBlockOptions;
+ /// <summary>An action to invoke for every accepted message.</summary>
+ private readonly Action<KeyValuePair<TInput, long>> _callAction;
+ /// <summary>Whether the block relies on the delegate to signal when an async operation has completed.</summary>
+ private readonly TargetCoreOptions _targetCoreOptions;
+ /// <summary>Bounding state for when the block is executing in bounded mode.</summary>
+ private readonly BoundingStateWithPostponed<TInput> _boundingState;
+ /// <summary>The reordering buffer used by the owner. May be null.</summary>
+ private readonly IReorderingBuffer _reorderingBuffer;
+
+ /// <summary>Gets the object used as the incoming lock.</summary>
+ private object IncomingLock { get { return _messages; } }
+
+ // *** These fields are mutated during execution.
+
+ /// <summary>Exceptions that may have occurred and gone unhandled during processing.</summary>
+ private List<Exception> _exceptions;
+ /// <summary>Whether to stop accepting new messages.</summary>
+ private bool _decliningPermanently;
+ /// <summary>The number of operations (including service tasks) currently running asynchronously.</summary>
+ /// <remarks>Must always be accessed from inside a lock.</remarks>
+ private int _numberOfOutstandingOperations;
+ /// <summary>The number of service tasks in async mode currently running.</summary>
+ /// <remarks>Must always be accessed from inside a lock.</remarks>
+ private int _numberOfOutstandingServiceTasks;
+ /// <summary>The next available ID we can assign to a message about to be processed.</summary>
+ private PaddedInt64 _nextAvailableInputMessageId; // initialized to 0... very important for a reordering buffer
+ /// <summary>A task has reserved the right to run the completion routine.</summary>
+ private bool _completionReserved;
+ /// <summary>This counter is set by the processing loop to prevent itself from trying to keep alive.</summary>
+ private int _keepAliveBanCounter;
+
+ /// <summary>Initializes the target core.</summary>
+ /// <param name="owningTarget">The target using this helper.</param>
+ /// <param name="callAction">An action to invoke for all accepted items.</param>
+ /// <param name="reorderingBuffer">The reordering buffer used by the owner; may be null.</param>
+ /// <param name="dataflowBlockOptions">The options to use to configure this block. The target core assumes these options are immutable.</param>
+ /// <param name="targetCoreOptions">Options for how the target core should behave.</param>
+ internal TargetCore(
+ ITargetBlock<TInput> owningTarget,
+ Action<KeyValuePair<TInput, long>> callAction,
+ IReorderingBuffer reorderingBuffer,
+ ExecutionDataflowBlockOptions dataflowBlockOptions,
+ TargetCoreOptions targetCoreOptions)
+ {
+ // Validate internal arguments
+ Contract.Requires(owningTarget != null, "Core must be associated with a target block.");
+ Contract.Requires(dataflowBlockOptions != null, "Options must be provided to configure the core.");
+ Contract.Requires(callAction != null, "Action to invoke for each item is required.");
+
+ // Store arguments and do additional initialization
+ _owningTarget = owningTarget;
+ _callAction = callAction;
+ _reorderingBuffer = reorderingBuffer;
+ _dataflowBlockOptions = dataflowBlockOptions;
+ _targetCoreOptions = targetCoreOptions;
+ _messages = (dataflowBlockOptions.MaxDegreeOfParallelism == 1) ?
+ (IProducerConsumerQueue<KeyValuePair<TInput, long>>)new SingleProducerSingleConsumerQueue<KeyValuePair<TInput, long>>() :
+ (IProducerConsumerQueue<KeyValuePair<TInput, long>>)new MultiProducerMultiConsumerQueue<KeyValuePair<TInput, long>>();
+ if (_dataflowBlockOptions.BoundedCapacity != System.Threading.Tasks.Dataflow.DataflowBlockOptions.Unbounded)
+ {
+ Debug.Assert(_dataflowBlockOptions.BoundedCapacity > 0, "Positive bounding count expected; should have been verified by options ctor");
+ _boundingState = new BoundingStateWithPostponed<TInput>(_dataflowBlockOptions.BoundedCapacity);
+ }
+ }
+
+ /// <summary>Internal Complete entry point with extra parameters for different contexts.</summary>
+ /// <param name="exception">If not null, the block will be faulted.</param>
+ /// <param name="dropPendingMessages">If true, any unprocessed input messages will be dropped.</param>
+ /// <param name="storeExceptionEvenIfAlreadyCompleting">If true, an exception will be stored after _decliningPermanently has been set to true.</param>
+ /// <param name="unwrapInnerExceptions">If true, exception will be treated as an AggregateException.</param>
+ /// <param name="revertProcessingState">Indicates whether the processing state is dirty and has to be reverted.</param>
+ internal void Complete(Exception exception, bool dropPendingMessages, bool storeExceptionEvenIfAlreadyCompleting = false,
+ bool unwrapInnerExceptions = false, bool revertProcessingState = false)
+ {
+ Contract.Requires(storeExceptionEvenIfAlreadyCompleting || !revertProcessingState,
+ "Indicating dirty processing state may only come with storeExceptionEvenIfAlreadyCompleting==true.");
+ Contract.EndContractBlock();
+
+ // Ensure that no new messages may be added
+ lock (IncomingLock)
+ {
+ // Faulting from outside is allowed until we start declining permanently.
+ // Faulting from inside is allowed at any time.
+ if (exception != null && (!_decliningPermanently || storeExceptionEvenIfAlreadyCompleting))
+ {
+ Debug.Assert(_numberOfOutstandingOperations > 0 || !storeExceptionEvenIfAlreadyCompleting,
+ "Calls with storeExceptionEvenIfAlreadyCompleting==true may only be coming from processing task.");
+
+#pragma warning disable 0420
+ Common.AddException(ref _exceptions, exception, unwrapInnerExceptions);
+ }
+
+ // Clear the messages queue if requested
+ if (dropPendingMessages)
+ {
+ KeyValuePair<TInput, long> dummy;
+ while (_messages.TryDequeue(out dummy)) ;
+ }
+
+ // Revert the dirty processing state if requested
+ if (revertProcessingState)
+ {
+ Debug.Assert(_numberOfOutstandingOperations > 0 && (!UsesAsyncCompletion || _numberOfOutstandingServiceTasks > 0),
+ "The processing state must be dirty when revertProcessingState==true.");
+ _numberOfOutstandingOperations--;
+ if (UsesAsyncCompletion) _numberOfOutstandingServiceTasks--;
+ }
+
+ // Trigger completion
+ _decliningPermanently = true;
+ CompleteBlockIfPossible();
+ }
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ internal DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> 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 shouldn't be accepting more messages, don't.
+ if (_decliningPermanently)
+ {
+ CompleteBlockIfPossible();
+ 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 no messages are currently being transfered to the input queue.
+ // (If there were any postponed messages, we would need to postpone so that ordering would be maintained.)
+ // (Unlike all other blocks, TargetCore can accept messages while processing, because
+ // input message IDs are properly assigned and the correct order is preserved.)
+ if (_boundingState == null ||
+ (_boundingState.OutstandingTransfers == 0 && _boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count == 0))
+ {
+ // 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, _owningTarget, out consumed);
+ if (!consumed) return DataflowMessageStatus.NotAvailable;
+ }
+
+ // Assign a message ID - strictly sequential, no gaps.
+ // Once consumed, enqueue the message with its ID and kick off asynchronous processing.
+ long messageId = _nextAvailableInputMessageId.Value++;
+ Debug.Assert(messageId != Common.INVALID_REORDERING_ID, "The assigned message ID is invalid.");
+ if (_boundingState != null) _boundingState.CurrentCount += 1; // track this new item against our bound
+ _messages.Enqueue(new KeyValuePair<TInput, long>(messageValue, messageId));
+ ProcessAsyncIfNecessary();
+ 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 non-greedy mode.");
+
+ // Store the message's info and kick off asynchronous processing
+ _boundingState.PostponedMessages.Push(source, messageHeader);
+ ProcessAsyncIfNecessary();
+ 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="Completion"]/*' />
+ internal Task Completion { get { return _completionSource.Task; } }
+
+ /// <summary>Gets the number of items waiting to be processed by this target.</summary>
+ internal int InputCount { get { return _messages.GetCountSafe(IncomingLock); } }
+
+ /// <summary>Signals to the target core that a previously launched asynchronous operation has now completed.</summary>
+ internal void SignalOneAsyncMessageCompleted()
+ {
+ SignalOneAsyncMessageCompleted(boundingCountChange: 0);
+ }
+
+ /// <summary>Signals to the target core that a previously launched asynchronous operation has now completed.</summary>
+ /// <param name="boundingCountChange">The number of elements by which to change the bounding count, if bounding is occurring.</param>
+ internal void SignalOneAsyncMessageCompleted(int boundingCountChange)
+ {
+ lock (IncomingLock)
+ {
+ // We're no longer processing, so decrement the DOP counter
+ Debug.Assert(_numberOfOutstandingOperations > 0, "Operations may only be completed if any are outstanding.");
+ if (_numberOfOutstandingOperations > 0) _numberOfOutstandingOperations--;
+
+ // Fix up the bounding count if necessary
+ if (_boundingState != null && boundingCountChange != 0)
+ {
+ Debug.Assert(boundingCountChange <= 0 && _boundingState.CurrentCount + boundingCountChange >= 0,
+ "Expected a negative bounding change and not to drop below zero.");
+ _boundingState.CurrentCount += boundingCountChange;
+ }
+
+ // 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.
+ ProcessAsyncIfNecessary(repeat: true);
+
+ // If, however, we stopped because we ran out of work to do and we
+ // know we'll never get more, then complete.
+ CompleteBlockIfPossible();
+ }
+ }
+
+ /// <summary>Gets whether this instance has been constructed for async processing.</summary>
+ private bool UsesAsyncCompletion
+ {
+ get
+ {
+ return (_targetCoreOptions & TargetCoreOptions.UsesAsyncCompletion) != 0;
+ }
+ }
+
+ /// <summary>Gets whether there's room to launch more processing operations.</summary>
+ private bool HasRoomForMoreOperations
+ {
+ get
+ {
+ Contract.Requires(_numberOfOutstandingOperations >= 0, "Number of outstanding operations should never be negative.");
+ Contract.Requires(_numberOfOutstandingServiceTasks >= 0, "Number of outstanding service tasks should never be negative.");
+ Contract.Requires(_numberOfOutstandingOperations >= _numberOfOutstandingServiceTasks, "Number of outstanding service tasks should never exceed the number of outstanding operations.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ // In async mode, we increment _numberOfOutstandingOperations before we start
+ // our own processing loop which should not count towards the MaxDOP.
+ return (_numberOfOutstandingOperations - _numberOfOutstandingServiceTasks) < _dataflowBlockOptions.ActualMaxDegreeOfParallelism;
+ }
+ }
+
+ /// <summary>Gets whether there's room to launch more service tasks for doing/launching processing operations.</summary>
+ private bool HasRoomForMoreServiceTasks
+ {
+ get
+ {
+ Contract.Requires(_numberOfOutstandingOperations >= 0, "Number of outstanding operations should never be negative.");
+ Contract.Requires(_numberOfOutstandingServiceTasks >= 0, "Number of outstanding service tasks should never be negative.");
+ Contract.Requires(_numberOfOutstandingOperations >= _numberOfOutstandingServiceTasks, "Number of outstanding service tasks should never exceed the number of outstanding operations.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (!UsesAsyncCompletion)
+ {
+ // Sync mode:
+ // We don't count service tasks, because our tasks are counted as operations.
+ // Therefore, return HasRoomForMoreOperations.
+ return HasRoomForMoreOperations;
+ }
+ else
+ {
+ // Async mode:
+ // We allow up to MaxDOP true service tasks.
+ // Checking whether there is room for more processing operations is not necessary,
+ // but doing so will help us avoid spinning up a task that will go away without
+ // launching any processing operation.
+ return HasRoomForMoreOperations &&
+ _numberOfOutstandingServiceTasks < _dataflowBlockOptions.ActualMaxDegreeOfParallelism;
+ }
+ }
+ }
+
+ /// <summary>Called when new messages are available to be processed.</summary>
+ /// <param name="repeat">Whether this call is the continuation of a previous message loop.</param>
+ private void ProcessAsyncIfNecessary(bool repeat = false)
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ if (HasRoomForMoreServiceTasks)
+ {
+ ProcessAsyncIfNecessary_Slow(repeat);
+ }
+ }
+
+ /// <summary>
+ /// Slow path for ProcessAsyncIfNecessary.
+ /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined.
+ /// </summary>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ private void ProcessAsyncIfNecessary_Slow(bool repeat)
+ {
+ Contract.Requires(HasRoomForMoreServiceTasks, "There must be room to process asynchronously.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ // Determine preconditions to launching a processing task
+ bool messagesAvailableOrPostponed =
+ !_messages.IsEmpty ||
+ (!_decliningPermanently && _boundingState != null && _boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count > 0);
+
+ // If all conditions are met, launch away
+ if (messagesAvailableOrPostponed && !CanceledOrFaulted)
+ {
+ // Any book keeping related to the processing task like incrementing the
+ // DOP counter or eventually recording the tasks reference must be done
+ // before the task starts. That is because the task itself will do the
+ // reverse operation upon its completion.
+ _numberOfOutstandingOperations++;
+ if (UsesAsyncCompletion) _numberOfOutstandingServiceTasks++;
+
+ var taskForInputProcessing = new Task(thisTargetCore => ((TargetCore<TInput>)thisTargetCore).ProcessMessagesLoopCore(), this,
+ Common.GetCreationOptionsForTask(repeat));
+
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.TaskLaunchedForMessageHandling(
+ _owningTarget, taskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages,
+ _messages.Count + (_boundingState != null ? _boundingState.PostponedMessages.Count : 0));
+ }
+#endif
+
+ // Start the task handling scheduling exceptions
+ Exception exception = Common.StartTaskSafe(taskForInputProcessing, _dataflowBlockOptions.TaskScheduler);
+ if (exception != null)
+ {
+ // Get out from under currently held locks. Complete re-acquires the locks it needs.
+ Task.Factory.StartNew(exc => Complete(exception: (Exception)exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true,
+ unwrapInnerExceptions: false, revertProcessingState: true),
+ exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+ }
+
+ /// <summary>Task body used to process messages.</summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void ProcessMessagesLoopCore()
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ KeyValuePair<TInput, long> messageWithId = default(KeyValuePair<TInput, long>);
+ try
+ {
+ bool useAsyncCompletion = UsesAsyncCompletion;
+ bool shouldAttemptPostponedTransfer = _boundingState != null && _boundingState.BoundedCapacity > 1;
+ int numberOfMessagesProcessedByThisTask = 0;
+ int numberOfMessagesProcessedSinceTheLastKeepAlive = 0;
+ int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask;
+
+ while (numberOfMessagesProcessedByThisTask < maxMessagesPerTask && !CanceledOrFaulted)
+ {
+ // If we're bounding, try to transfer a message from the postponed queue
+ // to the input queue. This enables us to more quickly unblock sources
+ // sending data to the block (otherwise, no postponed messages will be consumed
+ // until the input queue is entirely empty). If the bounded size is 1,
+ // there's no need to transfer, as attempting to get the next message will
+ // just go and consume the postponed message anyway, and we'll save
+ // the extra trip through the _messages queue.
+ KeyValuePair<TInput, long> transferMessageWithId;
+ if (shouldAttemptPostponedTransfer &&
+ TryConsumePostponedMessage(forPostponementTransfer: true, result: out transferMessageWithId))
+ {
+ lock (IncomingLock)
+ {
+ Debug.Assert(
+ _boundingState.OutstandingTransfers > 0
+ && _boundingState.OutstandingTransfers <= _dataflowBlockOptions.ActualMaxDegreeOfParallelism,
+ "Expected TryConsumePostponedMessage to have incremented the count and for the count to not exceed the DOP.");
+ _boundingState.OutstandingTransfers--; // was incremented in TryConsumePostponedMessage
+ _messages.Enqueue(transferMessageWithId);
+ ProcessAsyncIfNecessary();
+ }
+ }
+
+ if (useAsyncCompletion)
+ {
+ // Get the next message if DOP is available.
+ // If we can't get a message or DOP is not available, bail out.
+ if (!TryGetNextMessageForNewAsyncOperation(out messageWithId)) break;
+ }
+ else
+ {
+ // Try to get a message for sequential execution, i.e. without checking DOP availability
+ if (!TryGetNextAvailableOrPostponedMessage(out messageWithId))
+ {
+ // Try to keep the task alive only if MaxDOP=1
+ if (_dataflowBlockOptions.MaxDegreeOfParallelism != 1) break;
+
+ // If this task has processed enough messages without being kept alive,
+ // it has served its purpose. Don't keep it alive.
+ if (numberOfMessagesProcessedSinceTheLastKeepAlive > Common.KEEP_ALIVE_NUMBER_OF_MESSAGES_THRESHOLD) break;
+
+ // If keep alive is banned, don't attempt it
+ if (_keepAliveBanCounter > 0)
+ {
+ _keepAliveBanCounter--;
+ break;
+ }
+
+ // Reset the keep alive counter. (Keep this line together with TryKeepAliveUntil.)
+ numberOfMessagesProcessedSinceTheLastKeepAlive = 0;
+
+ // Try to keep the task alive briefly until a new message arrives
+ if (!Common.TryKeepAliveUntil(_keepAlivePredicate, this, out messageWithId))
+ {
+ // Keep alive was unsuccessful.
+ // Therefore ban further attempts temporarily.
+ _keepAliveBanCounter = Common.KEEP_ALIVE_BAN_COUNT;
+ break;
+ }
+ }
+ }
+
+ // We have popped a message from the queue.
+ // So increment the counter of processed messages.
+ numberOfMessagesProcessedByThisTask++;
+ numberOfMessagesProcessedSinceTheLastKeepAlive++;
+
+ // Invoke the user action
+ _callAction(messageWithId);
+ }
+ }
+ catch (Exception exc)
+ {
+ Common.StoreDataflowMessageValueIntoExceptionData(exc, messageWithId.Key);
+ Complete(exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false);
+ }
+ finally
+ {
+ lock (IncomingLock)
+ {
+ // We incremented _numberOfOutstandingOperations before we launched this task.
+ // So we must decremented it before exiting.
+ // Note that each async task additionally incremented it before starting and
+ // is responsible for decrementing it prior to exiting.
+ Debug.Assert(_numberOfOutstandingOperations > 0, "Expected a positive number of outstanding operations, since we're completing one here.");
+ _numberOfOutstandingOperations--;
+
+ // If we are in async mode, we've also incremented _numberOfOutstandingServiceTasks.
+ // Now it's time to decrement it.
+ if (UsesAsyncCompletion)
+ {
+ Debug.Assert(_numberOfOutstandingServiceTasks > 0, "Expected a positive number of outstanding service tasks, since we're completing one here.");
+ _numberOfOutstandingServiceTasks--;
+ }
+
+ // 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.
+ ProcessAsyncIfNecessary(repeat: true);
+
+ // If, however, we stopped because we ran out of work to do and we
+ // know we'll never get more, then complete.
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <summary>Retrieves the next message from the input queue for the useAsyncCompletion mode.</summary>
+ /// <param name="messageWithId">The next message retrieved.</param>
+ /// <returns>true if a message was found and removed; otherwise, false.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private bool TryGetNextMessageForNewAsyncOperation(out KeyValuePair<TInput, long> messageWithId)
+ {
+ Contract.Requires(UsesAsyncCompletion, "Only valid to use when in async mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ bool parallelismAvailable;
+
+ lock (IncomingLock)
+ {
+ // If we have room for another asynchronous operation, reserve it.
+ // If later it turns out that we had no work to fill the slot, we'll undo the addition.
+ parallelismAvailable = HasRoomForMoreOperations;
+ if (parallelismAvailable) ++_numberOfOutstandingOperations;
+ }
+
+ messageWithId = default(KeyValuePair<TInput, long>);
+ if (parallelismAvailable)
+ {
+ // If a parallelism slot was available, try to get an item.
+ // Be careful, because an exception may be thrown from ConsumeMessage
+ // and we have already incremented _numberOfOutstandingOperations.
+ bool gotMessage = false;
+ try
+ {
+ gotMessage = TryGetNextAvailableOrPostponedMessage(out messageWithId);
+ }
+ catch
+ {
+ // We have incremented the counter, but we didn't get a message.
+ // So we must undo the increment and eventually complete the block.
+ SignalOneAsyncMessageCompleted();
+
+ // Re-throw the exception. The processing loop will catch it.
+ throw;
+ }
+
+ // There may not be an error, but may have still failed to get a message.
+ // So we must undo the increment and eventually complete the block.
+ if (!gotMessage) SignalOneAsyncMessageCompleted();
+
+ return gotMessage;
+ }
+
+ // If there was no parallelism available, we didn't increment _numberOfOutstandingOperations.
+ // So there is nothing to do except to return false.
+ return false;
+ }
+
+ /// <summary>
+ /// Either takes the next available message from the input queue or retrieves a postponed
+ /// message from a source, based on whether we're in greedy or non-greedy mode.
+ /// </summary>
+ /// <param name="messageWithId">The retrieved item with its Id.</param>
+ /// <returns>true if a message could be removed and returned; otherwise, false.</returns>
+ private bool TryGetNextAvailableOrPostponedMessage(out KeyValuePair<TInput, long> messageWithId)
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // First try to get a message from our input buffer.
+ if (_messages.TryDequeue(out messageWithId))
+ {
+ return true;
+ }
+ // If we can't, but if we have any postponed messages due to bounding, then
+ // try to consume one of these postponed messages.
+ // Since we are not currently holding the lock, it is possible that new messages get queued up
+ // by the time we take the lock to manipulate _boundingState. So we have to double-check the
+ // input queue once we take the lock before we consider postponed messages.
+ else if (_boundingState != null && TryConsumePostponedMessage(forPostponementTransfer: false, result: out messageWithId))
+ {
+ return true;
+ }
+ // Otherwise, there's no message available.
+ else
+ {
+ messageWithId = default(KeyValuePair<TInput, long>);
+ return false;
+ }
+ }
+
+ /// <summary>Consumes a single postponed message.</summary>
+ /// <param name="forPostponementTransfer">
+ /// true if the method is being called to consume a message that'll then be stored into the input queue;
+ /// false if the method is being called to consume a message that'll be processed immediately.
+ /// If true, the bounding state's ForcePostponement will be updated.
+ /// If false, the method will first try (while holding the lock) to consume from the input queue before
+ /// consuming a postponed message.
+ /// </param>
+ /// <param name="result">The consumed message.</param>
+ /// <returns>true if a message was consumed; otherwise, false.</returns>
+ private bool TryConsumePostponedMessage(
+ bool forPostponementTransfer,
+ out KeyValuePair<TInput, long> result)
+ {
+ Contract.Requires(
+ _dataflowBlockOptions.BoundedCapacity !=
+ System.Threading.Tasks.Dataflow.DataflowBlockOptions.Unbounded, "Only valid to use when in bounded mode.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+
+ // Iterate until we either consume a message successfully or there are no more postponed messages.
+ bool countIncrementedExpectingToGetItem = false;
+ long messageId = Common.INVALID_REORDERING_ID;
+ while (true)
+ {
+ KeyValuePair<ISourceBlock<TInput>, DataflowMessageHeader> element;
+ lock (IncomingLock)
+ {
+ // If we are declining permanently, don't consume postponed messages.
+ if (_decliningPermanently) break;
+
+ // New messages may have been queued up while we weren't holding the lock.
+ // In particular, the input queue may have been filled up and messages may have
+ // gotten postponed. If we process such a postponed message, we would mess up the
+ // order. Therefore, we have to double-check the input queue first.
+ if (!forPostponementTransfer && _messages.TryDequeue(out result)) return true;
+
+ // We can consume a message to process if there's one to process and also if
+ // if we have logical room within our bound for the message.
+ if (!_boundingState.CountIsLessThanBound || !_boundingState.PostponedMessages.TryPop(out element))
+ {
+ if (countIncrementedExpectingToGetItem)
+ {
+ countIncrementedExpectingToGetItem = false;
+ _boundingState.CurrentCount -= 1;
+ }
+ break;
+ }
+ if (!countIncrementedExpectingToGetItem)
+ {
+ countIncrementedExpectingToGetItem = true;
+ messageId = _nextAvailableInputMessageId.Value++; // optimistically assign an ID
+ Debug.Assert(messageId != Common.INVALID_REORDERING_ID, "The assigned message ID is invalid.");
+ _boundingState.CurrentCount += 1; // optimistically take bounding space
+ if (forPostponementTransfer)
+ {
+ Debug.Assert(_boundingState.OutstandingTransfers >= 0, "Expected TryConsumePostponedMessage to not be negative.");
+ _boundingState.OutstandingTransfers++; // temporarily force postponement until we've successfully consumed the element
+ }
+ }
+ } // Must not call to source while holding lock
+
+ bool consumed;
+ TInput consumedValue = element.Key.ConsumeMessage(element.Value, _owningTarget, out consumed);
+ if (consumed)
+ {
+ result = new KeyValuePair<TInput, long>(consumedValue, messageId);
+ return true;
+ }
+ else
+ {
+ if (forPostponementTransfer)
+ {
+ // We didn't consume message so we need to decrement because we havent consumed the element.
+ _boundingState.OutstandingTransfers--;
+ }
+ }
+ }
+
+ // We optimistically acquired a message ID for a message that, in the end, we never got.
+ // So, we need to let the reordering buffer (if one exists) know that it should not
+ // expect an item with this ID. Otherwise, it would stall forever.
+ if (_reorderingBuffer != null && messageId != Common.INVALID_REORDERING_ID) _reorderingBuffer.IgnoreItem(messageId);
+
+ // Similarly, we optimistically increased the bounding count, expecting to get another message in.
+ // Since we didn't, we need to fix the bounding count back to what it should have been.
+ if (countIncrementedExpectingToGetItem) ChangeBoundingCount(-1);
+
+ // Inform the caller that no message could be consumed.
+ result = default(KeyValuePair<TInput, long>);
+ return false;
+ }
+
+ /// <summary>Gets whether the target has had cancellation requested or an exception has occurred.</summary>
+ private bool CanceledOrFaulted
+ {
+ get
+ {
+ return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || Volatile.Read(ref _exceptions) != null;
+ }
+ }
+
+ /// <summary>Completes the block once all completion conditions are met.</summary>
+ private void CompleteBlockIfPossible()
+ {
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ bool noMoreMessages = _decliningPermanently && _messages.IsEmpty;
+ if (noMoreMessages || CanceledOrFaulted)
+ {
+ CompleteBlockIfPossible_Slow();
+ }
+ }
+
+ /// <summary>
+ /// Slow path for CompleteBlockIfPossible.
+ /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined.
+ /// </summary>
+ private void CompleteBlockIfPossible_Slow()
+ {
+ Contract.Requires((_decliningPermanently && _messages.IsEmpty) || CanceledOrFaulted, "There must be no more messages.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: true);
+
+ bool notCurrentlyProcessing = _numberOfOutstandingOperations == 0;
+ if (notCurrentlyProcessing && !_completionReserved)
+ {
+ // Make sure no one else tries to call CompleteBlockOncePossible
+ _completionReserved = true;
+
+ // Make sure the target is declining
+ _decliningPermanently = true;
+
+ // Get out from under currently held locks. This is to avoid
+ // invoking synchronous continuations off of _completionSource.Task
+ // while holding a lock.
+ Task.Factory.StartNew(state => ((TargetCore<TInput>)state).CompleteBlockOncePossible(),
+ this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
+ }
+ }
+
+ /// <summary>
+ /// Completes the block. This must only be called once, and only once all of the completion conditions are met.
+ /// As such, it must only be called from CompleteBlockIfPossible.
+ /// </summary>
+ private void CompleteBlockOncePossible()
+ {
+ // Since the lock is needed only for the Assert, we do this only in DEBUG mode
+#if DEBUG
+ lock (IncomingLock) Debug.Assert(_numberOfOutstandingOperations == 0, "Everything must be done by now.");
+#endif
+
+ // Release any postponed messages
+ if (_boundingState != null)
+ {
+ // Note: No locks should be held at this point.
+ Common.ReleaseAllPostponedMessages(_owningTarget, _boundingState.PostponedMessages, ref _exceptions);
+ }
+
+ // For good measure and help in preventing leaks, clear out the incoming message queue,
+ // which may still contain orphaned data if we were canceled or faulted. However,
+ // we don't reset the bounding count here, as the block as a whole may still be active.
+ KeyValuePair<TInput, long> ignored;
+ IProducerConsumerQueue<KeyValuePair<TInput, long>> messages = _messages;
+ while (messages.TryDequeue(out ignored)) ;
+
+ // If we completed with any unhandled exception, finish in an error state
+ if (Volatile.Read(ref _exceptions) != null)
+ {
+ // It's ok to read _exceptions' content here, because
+ // at this point no more exceptions can be generated and thus no one will
+ // be writing to it.
+ _completionSource.TrySetException(Volatile.Read(ref _exceptions));
+ }
+ // If we completed with cancellation, finish in a canceled state
+ else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested)
+ {
+ _completionSource.TrySetCanceled();
+ }
+ // Otherwise, finish in a successful state.
+ else
+ {
+ _completionSource.TrySetResult(default(VoidResult));
+ }
+#if FEATURE_TRACING
+ // We only want to do tracing for block completion if this target core represents the whole block.
+ // If it only represents a part of the block (i.e. there's a source associated with it as well),
+ // then we shouldn't log just for the first half of the block; the source half will handle logging.
+ DataflowEtwProvider etwLog;
+ if ((_targetCoreOptions & TargetCoreOptions.RepresentsBlockCompletion) != 0 &&
+ (etwLog = DataflowEtwProvider.Log).IsEnabled())
+ {
+ etwLog.DataflowBlockCompleted(_owningTarget);
+ }
+#endif
+ }
+
+ /// <summary>Gets whether the target core is operating in a bounded mode.</summary>
+ internal bool IsBounded { get { return _boundingState != null; } }
+
+ /// <summary>Increases or decreases the bounding count.</summary>
+ /// <param name="count">The incremental addition (positive to increase, negative to decrease).</param>
+ internal void ChangeBoundingCount(int count)
+ {
+ Contract.Requires(count != 0, "Should only be called when the count is actually changing.");
+ Common.ContractAssertMonitorStatus(IncomingLock, held: false);
+ if (_boundingState != null)
+ {
+ lock (IncomingLock)
+ {
+ Debug.Assert(count > 0 || (count < 0 && _boundingState.CurrentCount + count >= 0),
+ "If count is negative, it must not take the total count negative.");
+ _boundingState.CurrentCount += count;
+ ProcessAsyncIfNecessary();
+ CompleteBlockIfPossible();
+ }
+ }
+ }
+
+ /// <summary>Gets the object to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displayTarget = _owningTarget as IDebuggerDisplay;
+ return string.Format("Block=\"{0}\"",
+ displayTarget != null ? displayTarget.Content : _owningTarget);
+ }
+ }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } }
+
+ /// <summary>Gets information about this helper to be used for display in a debugger.</summary>
+ /// <returns>Debugging information about this target.</returns>
+ internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); }
+
+ /// <summary>Provides a wrapper for commonly needed debugging information.</summary>
+ internal sealed class DebuggingInformation
+ {
+ /// <summary>The target being viewed.</summary>
+ private readonly TargetCore<TInput> _target;
+
+ /// <summary>Initializes the debugging helper.</summary>
+ /// <param name="target">The target being viewed.</param>
+ internal DebuggingInformation(TargetCore<TInput> target) { _target = target; }
+
+ /// <summary>Gets the number of messages waiting to be processed.</summary>
+ internal int InputCount { get { return _target._messages.Count; } }
+ /// <summary>Gets the messages waiting to be processed.</summary>
+ internal IEnumerable<TInput> InputQueue { get { return _target._messages.Select(kvp => kvp.Key).ToList(); } }
+
+ /// <summary>Gets any postponed messages.</summary>
+ internal QueuedMap<ISourceBlock<TInput>, DataflowMessageHeader> PostponedMessages
+ {
+ get { return _target._boundingState != null ? _target._boundingState.PostponedMessages : null; }
+ }
+
+ /// <summary>Gets the current number of outstanding input processing operations.</summary>
+ internal Int32 CurrentDegreeOfParallelism { get { return _target._numberOfOutstandingOperations - _target._numberOfOutstandingServiceTasks; } }
+
+ /// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
+ internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } }
+ /// <summary>Gets whether the block is declining further messages.</summary>
+ internal bool IsDecliningPermanently { get { return _target._decliningPermanently; } }
+ /// <summary>Gets whether the block is completed.</summary>
+ internal bool IsCompleted { get { return _target.Completion.IsCompleted; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// TargetRegistry.cs
+//
+//
+// A store of registered targets with a target block.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Linq;
+
+namespace System.Threading.Tasks.Dataflow.Internal
+{
+ /// <summary>Stores targets registered with a source.</summary>
+ /// <typeparam name="T">Specifies the type of data accepted by the targets.</typeparam>
+ /// <remarks>This type is not thread-safe.</remarks>
+ [DebuggerDisplay("Count={Count}")]
+ [DebuggerTypeProxy(typeof(TargetRegistry<>.DebugView))]
+ internal sealed class TargetRegistry<T>
+ {
+ /// <summary>
+ /// Information about a registered target. This class represents a self-sufficient node in a linked list.
+ /// </summary>
+ internal sealed class LinkedTargetInfo
+ {
+ /// <summary>Initializes the LinkedTargetInfo.</summary>
+ /// <param name="target">The target block reference for this entry.</param>
+ /// <param name="linkOptions">The link options.</param>
+ internal LinkedTargetInfo(ITargetBlock<T> target, DataflowLinkOptions linkOptions)
+ {
+ Contract.Requires(target != null, "The target that is supposed to be linked must not be null.");
+ Contract.Requires(linkOptions != null, "The linkOptions must not be null.");
+
+ Target = target;
+ PropagateCompletion = linkOptions.PropagateCompletion;
+ RemainingMessages = linkOptions.MaxMessages;
+ }
+
+ /// <summary>The target block reference for this entry.</summary>
+ internal readonly ITargetBlock<T> Target;
+ /// <summary>The value of the PropagateCompletion link option.</summary>
+ internal readonly bool PropagateCompletion;
+ /// <summary>Number of remaining messages to propagate.
+ /// This counter is initialized to the MaxMessages option and
+ /// gets decremented after each successful propagation.</summary>
+ internal int RemainingMessages;
+ /// <summary>The previous node in the list.</summary>
+ internal LinkedTargetInfo Previous;
+ /// <summary>The next node in the list.</summary>
+ internal LinkedTargetInfo Next;
+ }
+
+ /// <summary>A reference to the owning source block.</summary>
+ private readonly ISourceBlock<T> _owningSource;
+ /// <summary>A mapping of targets to information about them.</summary>
+ private readonly Dictionary<ITargetBlock<T>, LinkedTargetInfo> _targetInformation;
+ /// <summary>The first node of an ordered list of targets. Messages should be offered to targets starting from First and following Next.</summary>
+ private LinkedTargetInfo _firstTarget;
+ /// <summary>The last node of the ordered list of targets. This field is used purely as a perf optimization to avoid traversing the list for each Add.</summary>
+ private LinkedTargetInfo _lastTarget;
+ /// <summary>Number of links with positive RemainingMessages counters.
+ /// This is an optimization that allows us to skip dictionary lookup when this counter is 0.</summary>
+ private int _linksWithRemainingMessages;
+
+ /// <summary>Initializes the registry.</summary>
+ internal TargetRegistry(ISourceBlock<T> owningSource)
+ {
+ Contract.Requires(owningSource != null, "The TargetRegistry instance must be owned by a source block.");
+
+ _owningSource = owningSource;
+ _targetInformation = new Dictionary<ITargetBlock<T>, LinkedTargetInfo>();
+ }
+
+ /// <summary>Adds a target to the registry.</summary>
+ /// <param name="target">The target to add.</param>
+ /// <param name="linkOptions">The link options.</param>
+ internal void Add(ref ITargetBlock<T> target, DataflowLinkOptions linkOptions)
+ {
+ Contract.Requires(target != null, "The target that is supposed to be linked must not be null.");
+ Contract.Requires(linkOptions != null, "The link options must not be null.");
+
+ LinkedTargetInfo targetInfo;
+
+ // If the target already exists in the registry, replace it with a new NopLinkPropagator to maintain uniqueness
+ if (_targetInformation.TryGetValue(target, out targetInfo)) target = new NopLinkPropagator(_owningSource, target);
+
+ // Add the target to both stores, the list and the dictionary, which are used for different purposes
+ var node = new LinkedTargetInfo(target, linkOptions);
+ AddToList(node, linkOptions.Append);
+ _targetInformation.Add(target, node);
+
+ // Increment the optimization counter if needed
+ Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
+ if (node.RemainingMessages > 0) _linksWithRemainingMessages++;
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockLinking(_owningSource, target);
+ }
+#endif
+ }
+
+ /// <summary>Gets whether the registry contains a particular target.</summary>
+ /// <param name="target">The target.</param>
+ /// <returns>true if the registry contains the target; otherwise, false.</returns>
+ internal bool Contains(ITargetBlock<T> target)
+ {
+ return _targetInformation.ContainsKey(target);
+ }
+
+ /// <summary>Removes the target from the registry.</summary>
+ /// <param name="target">The target to remove.</param>
+ /// <param name="onlyIfReachedMaxMessages">
+ /// Only remove the target if it's configured to be unlinked after one propagation.
+ /// </param>
+ internal void Remove(ITargetBlock<T> target, bool onlyIfReachedMaxMessages = false)
+ {
+ Contract.Requires(target != null, "Target to remove is required.");
+
+ // If we are implicitly unlinking and there is nothing to be unlinked implicitly, bail
+ Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
+ if (onlyIfReachedMaxMessages && _linksWithRemainingMessages == 0) return;
+
+ // Otherwise take the slow path
+ Remove_Slow(target, onlyIfReachedMaxMessages);
+ }
+
+ /// <summary>Actually removes the target from the registry.</summary>
+ /// <param name="target">The target to remove.</param>
+ /// <param name="onlyIfReachedMaxMessages">
+ /// Only remove the target if it's configured to be unlinked after one propagation.
+ /// </param>
+ private void Remove_Slow(ITargetBlock<T> target, bool onlyIfReachedMaxMessages)
+ {
+ Contract.Requires(target != null, "Target to remove is required.");
+
+ // Make sure we've intended to go the slow route
+ Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
+ Debug.Assert(!onlyIfReachedMaxMessages || _linksWithRemainingMessages > 0, "We shouldn't have ended on the slow path.");
+
+ // If the target is registered...
+ LinkedTargetInfo node;
+ if (_targetInformation.TryGetValue(target, out node))
+ {
+ Debug.Assert(node != null, "The LinkedTargetInfo node referenced in the Dictionary must be non-null.");
+
+ // Remove the target, if either there's no constraint on the removal
+ // or if this was the last remaining message.
+ if (!onlyIfReachedMaxMessages || node.RemainingMessages == 1)
+ {
+ RemoveFromList(node);
+ _targetInformation.Remove(target);
+
+ // Decrement the optimization counter if needed
+ if (node.RemainingMessages == 0) _linksWithRemainingMessages--;
+ Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
+#if FEATURE_TRACING
+ DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.DataflowBlockUnlinking(_owningSource, target);
+ }
+#endif
+ }
+ // If the target is to stay and we are counting the remaining messages for this link, decrement the counter
+ else if (node.RemainingMessages > 0)
+ {
+ Debug.Assert(node.RemainingMessages > 1, "The target should have been removed, because there are no remaining messages.");
+ node.RemainingMessages--;
+ }
+ }
+ }
+
+ /// <summary>Clears the target registry entry points while allowing subsequent traversals of the linked list.</summary>
+ internal LinkedTargetInfo ClearEntryPoints()
+ {
+ // Save _firstTarget so we can return it
+ LinkedTargetInfo firstTarget = _firstTarget;
+
+ // Clear out the entry points
+ _firstTarget = _lastTarget = null;
+ _targetInformation.Clear();
+ Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
+ _linksWithRemainingMessages = 0;
+
+ return firstTarget;
+ }
+
+ /// <summary>Propagated completion to the targets of the given linked list.</summary>
+ /// <param name="firstTarget">The head of a saved linked list.</param>
+ internal void PropagateCompletion(LinkedTargetInfo firstTarget)
+ {
+ Debug.Assert(_owningSource.Completion.IsCompleted, "The owning source must have completed before propagating completion.");
+
+ // Cache the owning source's completion task to avoid calling the getter many times
+ Task owningSourceCompletion = _owningSource.Completion;
+
+ // Propagate completion to those targets that have requested it
+ for (LinkedTargetInfo node = firstTarget; node != null; node = node.Next)
+ {
+ if (node.PropagateCompletion) Common.PropagateCompletion(owningSourceCompletion, node.Target, Common.AsyncExceptionHandler);
+ }
+ }
+
+ /// <summary>Gets the first node of the ordered target list.</summary>
+ internal LinkedTargetInfo FirstTargetNode { get { return _firstTarget; } }
+
+ /// <summary>Adds a LinkedTargetInfo node to the doubly-linked list.</summary>
+ /// <param name="node">The node to be added.</param>
+ /// <param name="append">Whether to append or to prepend the node.</param>
+ internal void AddToList(LinkedTargetInfo node, bool append)
+ {
+ Contract.Requires(node != null, "Requires a node to be added.");
+
+ // If the list is empty, assign the ends to point to the new node and we are done
+ if (_firstTarget == null && _lastTarget == null)
+ {
+ _firstTarget = _lastTarget = node;
+ }
+ else
+ {
+ Debug.Assert(_firstTarget != null && _lastTarget != null, "Both first and last node must either be null or non-null.");
+ Debug.Assert(_lastTarget.Next == null, "The last node must not have a successor.");
+ Debug.Assert(_firstTarget.Previous == null, "The first node must not have a predecessor.");
+
+ if (append)
+ {
+ // Link the new node to the end of the existing list
+ node.Previous = _lastTarget;
+ _lastTarget.Next = node;
+ _lastTarget = node;
+ }
+ else
+ {
+ // Link the new node to the front of the existing list
+ node.Next = _firstTarget;
+ _firstTarget.Previous = node;
+ _firstTarget = node;
+ }
+ }
+
+ Debug.Assert(_firstTarget != null && _lastTarget != null, "Both first and last node must be non-null after AddToList.");
+ }
+
+ /// <summary>Removes the LinkedTargetInfo node from the doubly-linked list.</summary>
+ /// <param name="node">The node to be removed.</param>
+ internal void RemoveFromList(LinkedTargetInfo node)
+ {
+ Contract.Requires(node != null, "Node to remove is required.");
+ Debug.Assert(_firstTarget != null && _lastTarget != null, "Both first and last node must be non-null before RemoveFromList.");
+
+ LinkedTargetInfo previous = node.Previous;
+ LinkedTargetInfo next = node.Next;
+
+ // Remove the node by linking the adjacent nodes
+ if (node.Previous != null)
+ {
+ node.Previous.Next = next;
+ node.Previous = null;
+ }
+
+ if (node.Next != null)
+ {
+ node.Next.Previous = previous;
+ node.Next = null;
+ }
+
+ // Adjust the list ends
+ if (_firstTarget == node) _firstTarget = next;
+ if (_lastTarget == node) _lastTarget = previous;
+
+ Debug.Assert((_firstTarget != null) == (_lastTarget != null), "Both first and last node must either be null or non-null after RemoveFromList.");
+ }
+
+ /// <summary>Gets the number of items in the registry.</summary>
+ private int Count { get { return _targetInformation.Count; } }
+
+ /// <summary>Converts the linked list of targets to an array for rendering in a debugger.</summary>
+ private ITargetBlock<T>[] TargetsForDebugger
+ {
+ get
+ {
+ var targets = new ITargetBlock<T>[Count];
+ int i = 0;
+ for (LinkedTargetInfo node = _firstTarget; node != null; node = node.Next)
+ {
+ targets[i++] = node.Target;
+ }
+
+ return targets;
+ }
+ }
+
+
+
+ /// <summary>Provides a nop passthrough for use with TargetRegistry.</summary>
+ [DebuggerDisplay("{DebuggerDisplayContent,nq}")]
+ [DebuggerTypeProxy(typeof(TargetRegistry<>.NopLinkPropagator.DebugView))]
+ private sealed class NopLinkPropagator : IPropagatorBlock<T, T>, ISourceBlock<T>, IDebuggerDisplay
+ {
+ /// <summary>The source that encapsulates this block.</summary>
+ private readonly ISourceBlock<T> _owningSource;
+ /// <summary>The target with which this block is associated.</summary>
+ private readonly ITargetBlock<T> _target;
+
+ /// <summary>Initializes the passthrough.</summary>
+ /// <param name="owningSource">The source that encapsulates this block.</param>
+ /// <param name="target">The target to which messages should be forwarded.</param>
+ internal NopLinkPropagator(ISourceBlock<T> owningSource, ITargetBlock<T> target)
+ {
+ Contract.Requires(owningSource != null, "Propagator must be associated with a source.");
+ Contract.Requires(target != null, "Target to propagate to is required.");
+
+ // Store the arguments
+ _owningSource = owningSource;
+ _target = target;
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
+ DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source, Boolean consumeToAccept)
+ {
+ Debug.Assert(source == _owningSource, "Only valid to be used with the source for which it was created.");
+ return _target.OfferMessage(messageHeader, messageValue, this, consumeToAccept);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
+ T ISourceBlock<T>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target, out Boolean messageConsumed)
+ {
+ return _owningSource.ConsumeMessage(messageHeader, this, out messageConsumed);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
+ bool ISourceBlock<T>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
+ {
+ return _owningSource.ReserveMessage(messageHeader, this);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
+ void ISourceBlock<T>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
+ {
+ _owningSource.ReleaseReservation(messageHeader, this);
+ }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
+ Task IDataflowBlock.Completion { get { return _owningSource.Completion; } }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
+ void IDataflowBlock.Complete() { _target.Complete(); }
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
+ void IDataflowBlock.Fault(Exception exception) { _target.Fault(exception); }
+
+ /// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
+ IDisposable ISourceBlock<T>.LinkTo(ITargetBlock<T> target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
+
+ /// <summary>The data to display in the debugger display attribute.</summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
+ private object DebuggerDisplayContent
+ {
+ get
+ {
+ var displaySource = _owningSource as IDebuggerDisplay;
+ var displayTarget = _target as IDebuggerDisplay;
+ return string.Format("{0} Source=\"{1}\", Target=\"{2}\"",
+ Common.GetNameForDebugger(this),
+ displaySource != null ? displaySource.Content : _owningSource,
+ displayTarget != null ? displayTarget.Content : _target);
+ }
+ }
+ /// <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 a passthrough.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The passthrough.</summary>
+ private readonly NopLinkPropagator _passthrough;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="passthrough">The passthrough to view.</param>
+ public DebugView(NopLinkPropagator passthrough)
+ {
+ Contract.Requires(passthrough != null, "Need a propagator with which to construct the debug view.");
+ _passthrough = passthrough;
+ }
+
+ /// <summary>The linked target for this block.</summary>
+ public ITargetBlock<T> LinkedTarget { get { return _passthrough._target; } }
+ }
+ }
+
+
+ /// <summary>Provides a debugger type proxy for the target registry.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The registry being debugged.</summary>
+ private readonly TargetRegistry<T> _registry;
+
+ /// <summary>Initializes the type proxy.</summary>
+ /// <param name="registry">The target registry.</param>
+ public DebugView(TargetRegistry<T> registry)
+ {
+ Contract.Requires(registry != null, "Need a registry with which to construct the debug view.");
+ _registry = registry;
+ }
+
+ /// <summary>Gets a list of all targets to show in the debugger.</summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public ITargetBlock<T>[] Targets { get { return _registry.TargetsForDebugger; } }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Threading.Tasks.Dataflow.Internal.Threading
+{
+ internal delegate void TimerCallback(object state);
+
+ internal sealed class Timer : CancellationTokenSource, IDisposable
+ {
+ internal Timer(TimerCallback callback, object state, int dueTime, int period)
+ {
+ Debug.Assert(period == -1, "This stub implementation only supports dueTime.");
+ Task.Delay(dueTime, Token).ContinueWith((t, s) =>
+ {
+ var tuple = (Tuple<TimerCallback, object>)s;
+ tuple.Item1(tuple.Item2);
+ }, Tuple.Create(callback, state), CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
+ TaskScheduler.Default);
+ }
+
+ public new void Dispose() { base.Cancel(); }
+ }
+
+ internal sealed class Thread
+ {
+ internal static bool Yield() { return true; }
+ }
+
+ internal delegate void WaitCallback(object state);
+
+ internal sealed class ThreadPool
+ {
+ private static readonly SynchronizationContext _ctx = new SynchronizationContext();
+
+ internal static void QueueUserWorkItem(WaitCallback callback, object state)
+ {
+ _ctx.Post(s =>
+ {
+ var tuple = (Tuple<WaitCallback, object>)s;
+ tuple.Item1(tuple.Item2);
+ }, Tuple.Create(callback, state));
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="ArgumentOutOfRange_BatchSizeMustBeNoGreaterThanBoundedCapacity" xml:space="preserve">
+ <value>Number must be no greater than the value specified in BoundedCapacity.</value>
+ </data>
+ <data name="ArgumentOutOfRange_GenericPositive" xml:space="preserve">
+ <value>Number must be positive.</value>
+ </data>
+ <data name="ArgumentOutOfRange_NeedNonNegOrNegative1" xml:space="preserve">
+ <value>Number must be either non-negative and less than or equal to Int32.MaxValue or -1</value>
+ </data>
+ <data name="Argument_BoundedCapacityNotSupported" xml:space="preserve">
+ <value>BoundedCapacity must be Unbounded or -1 for this dataflow block.</value>
+ </data>
+ <data name="Argument_CantConsumeFromANullSource" xml:space="preserve">
+ <value>The argument must be false if no source from which to consume is specified.</value>
+ </data>
+ <data name="Argument_InvalidMessageHeader" xml:space="preserve">
+ <value>The DataflowMessageHeader instance does not represent a valid message header.</value>
+ </data>
+ <data name="Argument_InvalidMessageId" xml:space="preserve">
+ <value>To construct a DataflowMessageHeader instance, either pass a non-zero value or use the parameterless constructor.</value>
+ </data>
+ <data name="Argument_InvalidSourceForFilteredLink" xml:space="preserve">
+ <value>This block must only be used with the source from which it was created.</value>
+ </data>
+ <data name="Argument_NonGreedyNotSupported" xml:space="preserve">
+ <value>Greedy must be true for this dataflow block.</value>
+ </data>
+ <data name="event_DataflowBlockCompleted" xml:space="preserve">
+ <value>Block {0} completed as {1}. {2}</value>
+ </data>
+ <data name="event_DataflowBlockCreated" xml:space="preserve">
+ <value>Block of type {0} instantiated with Id {1}.</value>
+ </data>
+ <data name="event_DataflowBlockLinking" xml:space="preserve">
+ <value>Source {0} linked to target {1}.</value>
+ </data>
+ <data name="event_DataflowBlockUnlinking" xml:space="preserve">
+ <value>Source {0} unlinked from target {1}.</value>
+ </data>
+ <data name="event_TaskLaunchedForMessageHandling" xml:space="preserve">
+ <value>{1} task launched from block {0} with {2} message(s) pending.</value>
+ </data>
+ <data name="InvalidOperation_DataNotAvailableForReceive" xml:space="preserve">
+ <value>The source completed without providing data to receive.</value>
+ </data>
+ <data name="InvalidOperation_FailedToConsumeReservedMessage" xml:space="preserve">
+ <value>The target block failed to consume a message it had successfully reserved.</value>
+ </data>
+ <data name="InvalidOperation_MessageNotReservedByTarget" xml:space="preserve">
+ <value>The target does not have the message reserved.</value>
+ </data>
+ <data name="NotSupported_MemberNotNeeded" xml:space="preserve">
+ <value>This member is not supported on this dataflow block. The block is intended for a specific purpose that does not utilize this member.</value>
+ </data>
+ <data name="ConcurrentCollection_SyncRoot_NotSupported" xml:space="preserve">
+ <value>The SyncRoot property may not be used for the synchronization of concurrent collections.</value>
+ </data>
+</root>
\ No newline at end of file
include ../../build/library.make
LIB_REFS += System.Core System
-LIB_MCS_FLAGS += -r:$(corlib)
+LIB_MCS_FLAGS += -r:$(corlib) -d:CONCURRENT_COLLECTIONS
TEST_MCS_FLAGS = -r:System.Core.dll -r:System.dll
+EXTRA_DISTFILES=README.md
--- /dev/null
+The CoreFxSources folder contains the implementation taken from MS CoreFx
+repository at 905a1940bcda0afdca2f14ceb2b0161ebc4d1d02.
+
+While we'd ideally not ship this assembly at all with Mono (it doesn't ship
+with .NET Framework, there's only as a NuGet package), we shipped it in
+the past and as such people might rely on it so we can't remove it.
--- /dev/null
+//
+// Resource strings referenced by the code.
+//
+// Copyright 2015 Xamarin Inc
+//
+// Use the following script to extract strings from CoreFxSources/Resources/Strings.resx
+//
+// var d = XDocument.Load ("Strings.resx");
+// foreach (var j in d.XPathSelectElements ("/root/data")){ var v = j.XPathSelectElement ("value"); Console.WriteLine ("\tpublic const string {0}=\"{1}\";", j.Attribute ("name").Value, v.Value); }
+//
+partial class SR
+{
+ public const string ArgumentOutOfRange_BatchSizeMustBeNoGreaterThanBoundedCapacity="Number must be no greater than the value specified in BoundedCapacity.";
+ public const string ArgumentOutOfRange_GenericPositive="Number must be positive.";
+ public const string ArgumentOutOfRange_NeedNonNegOrNegative1="Number must be either non-negative and less than or equal to Int32.MaxValue or -1";
+ public const string Argument_BoundedCapacityNotSupported="BoundedCapacity must be Unbounded or -1 for this dataflow block.";
+ public const string Argument_CantConsumeFromANullSource="The argument must be false if no source from which to consume is specified.";
+ public const string Argument_InvalidMessageHeader="The DataflowMessageHeader instance does not represent a valid message header.";
+ public const string Argument_InvalidMessageId="To construct a DataflowMessageHeader instance, either pass a non-zero value or use the parameterless constructor.";
+ public const string Argument_InvalidSourceForFilteredLink="This block must only be used with the source from which it was created.";
+ public const string Argument_NonGreedyNotSupported="Greedy must be true for this dataflow block.";
+ public const string InvalidOperation_DataNotAvailableForReceive="The source completed without providing data to receive.";
+ public const string InvalidOperation_FailedToConsumeReservedMessage="The target block failed to consume a message it had successfully reserved.";
+ public const string InvalidOperation_MessageNotReservedByTarget="The target does not have the message reserved.";
+ public const string NotSupported_MemberNotNeeded="This member is not supported on this dataflow block. The block is intended for a specific purpose that does not utilize this member.";
+ public const string ConcurrentCollection_SyncRoot_NotSupported="The SyncRoot property may not be used for the synchronization of concurrent collections.";
+}
\ No newline at end of file
../../build/common/Consts.cs
../../build/common/Locale.cs
../../build/common/MonoTODOAttribute.cs
+../../build/common/SR.cs
+SR.cs
Assembly/AssemblyInfo.cs
-System.Threading.Tasks.Dataflow/ExecutingMessageBox.cs
-System.Threading.Tasks.Dataflow/DataflowBlockOptions.cs
-System.Threading.Tasks.Dataflow/DataflowMessageHeader.cs
-System.Threading.Tasks.Dataflow/DataflowMessageStatus.cs
-System.Threading.Tasks.Dataflow/ExecutionDataflowBlockOptions.cs
-System.Threading.Tasks.Dataflow/GroupingDataflowBlockOptions.cs
-System.Threading.Tasks.Dataflow/DataflowLinkOptions.cs
-System.Threading.Tasks.Dataflow/IDataflowBlock.cs
-System.Threading.Tasks.Dataflow/IPropagatorBlock.cs
-System.Threading.Tasks.Dataflow/IReceivableSourceBlock.cs
-System.Threading.Tasks.Dataflow/ISourceBlock.cs
-System.Threading.Tasks.Dataflow/ITargetBlock.cs
-System.Threading.Tasks.Dataflow/CompletionHelper.cs
-System.Threading.Tasks.Dataflow/MessageBox.cs
-System.Threading.Tasks.Dataflow/OutgoingQueueBase.cs
-System.Threading.Tasks.Dataflow/OutgoingQueue.cs
-System.Threading.Tasks.Dataflow/BroadcastOutgoingQueue.cs
-System.Threading.Tasks.Dataflow/PassingMessageBox.cs
-System.Threading.Tasks.Dataflow/NameHelper.cs
-System.Threading.Tasks.Dataflow/TargetCollection.cs
-System.Threading.Tasks.Dataflow/JoinTarget.cs
-../Mono.Parallel/Mono.Threading/AtomicBoolean.cs
-System.Threading.Tasks.Dataflow/ActionBlock.cs
-System.Threading.Tasks.Dataflow/BatchBlock.cs
-System.Threading.Tasks.Dataflow/BatchedJoinBlock.cs
-System.Threading.Tasks.Dataflow/BatchedJoinBlock`3.cs
-System.Threading.Tasks.Dataflow/BroadcastBlock.cs
-System.Threading.Tasks.Dataflow/BufferBlock.cs
-System.Threading.Tasks.Dataflow/ChooserBlock.cs
-System.Threading.Tasks.Dataflow/DataflowBlock.cs
-System.Threading.Tasks.Dataflow/JoinBlock.cs
-System.Threading.Tasks.Dataflow/JoinBlock`3.cs
-System.Threading.Tasks.Dataflow/ObservableDataflowBlock.cs
-System.Threading.Tasks.Dataflow/ObserverDataflowBlock.cs
-System.Threading.Tasks.Dataflow/PropagatorWrapperBlock.cs
-System.Threading.Tasks.Dataflow/ReceiveBlock.cs
-System.Threading.Tasks.Dataflow/TransformBlock.cs
-System.Threading.Tasks.Dataflow/TransformManyBlock.cs
-System.Threading.Tasks.Dataflow/WriteOnceBlock.cs
-System.Threading.Tasks.Dataflow/SendBlock.cs
-System.Threading.Tasks.Dataflow/PredicateBlock.cs
-System.Threading.Tasks.Dataflow/OutputAvailableBlock.cs
-System.Threading.Tasks.Dataflow/NullTargetBlock.cs
-System.Threading.Tasks.Dataflow/AsyncExecutingMessageBox.cs
-System.Threading.Tasks.Dataflow/ExecutingMessageBoxBase.cs
+CoreFxSources/Base/DataflowBlock.cs
+CoreFxSources/Base/DataflowBlockOptions.cs
+CoreFxSources/Base/DataflowLinkOptions.cs
+CoreFxSources/Base/DataflowMessageHeader.cs
+CoreFxSources/Base/DataflowMessageStatus.cs
+CoreFxSources/Base/IDataflowBlock.cs
+CoreFxSources/Base/IPropagatorBlock.cs
+CoreFxSources/Base/IReceivableSourceBlock.cs
+CoreFxSources/Base/ISourceBlock.cs
+CoreFxSources/Base/ITargetBlock.cs
+CoreFxSources/Blocks/ActionBlock.cs
+CoreFxSources/Blocks/BatchBlock.cs
+CoreFxSources/Blocks/BatchedJoinBlock.cs
+CoreFxSources/Blocks/BroadcastBlock.cs
+CoreFxSources/Blocks/BufferBlock.cs
+CoreFxSources/Blocks/JoinBlock.cs
+CoreFxSources/Blocks/TransformBlock.cs
+CoreFxSources/Blocks/TransformManyBlock.cs
+CoreFxSources/Blocks/WriteOnceBlock.cs
+CoreFxSources/Internal/ActionOnDispose.cs
+CoreFxSources/Internal/Common.cs
+CoreFxSources/Internal/EnumerableDebugView.cs
+CoreFxSources/Internal/IDebuggerDisplay.cs
+CoreFxSources/Internal/ImmutableList.cs
+CoreFxSources/Internal/Padding.cs
+CoreFxSources/Internal/ProducerConsumerQueues.cs
+CoreFxSources/Internal/QueuedMap.cs
+CoreFxSources/Internal/ReorderingBuffer.cs
+CoreFxSources/Internal/SourceCore.cs
+CoreFxSources/Internal/SpscTargetCore.cs
+CoreFxSources/Internal/TargetCore.cs
+CoreFxSources/Internal/TargetRegistry.cs
+CoreFxSources/Internal/Threading.cs
\ No newline at end of file
+++ /dev/null
-// ActionBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class ActionBlock<TInput> : ITargetBlock<TInput> {
- readonly CompletionHelper compHelper;
- readonly BlockingCollection<TInput> messageQueue = new BlockingCollection<TInput> ();
- readonly ExecutingMessageBoxBase<TInput> messageBox;
- readonly Action<TInput> action;
- readonly Func<TInput, Task> asyncAction;
- readonly ExecutionDataflowBlockOptions dataflowBlockOptions;
-
- ActionBlock (ExecutionDataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = new CompletionHelper (dataflowBlockOptions);
- }
-
- public ActionBlock (Action<TInput> action)
- : this (action, ExecutionDataflowBlockOptions.Default)
- {
- }
-
- public ActionBlock (Action<TInput> action,
- ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this (dataflowBlockOptions)
- {
- if (action == null)
- throw new ArgumentNullException ("action");
-
- this.action = action;
- this.messageBox = new ExecutingMessageBox<TInput> (this, messageQueue, compHelper,
- () => true, ProcessItem, () => { }, dataflowBlockOptions);
- }
-
- public ActionBlock (Func<TInput, Task> action)
- : this (action, ExecutionDataflowBlockOptions.Default)
- {
- }
-
- public ActionBlock (Func<TInput, Task> action,
- ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this (dataflowBlockOptions)
- {
- if (action == null)
- throw new ArgumentNullException ("action");
-
- this.asyncAction = action;
- this.messageBox = new AsyncExecutingMessageBox<TInput, Task> (
- this, messageQueue, compHelper, () => true, AsyncProcessItem, null,
- () => { }, dataflowBlockOptions);
- }
-
- DataflowMessageStatus ITargetBlock<TInput>.OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept)
- {
- return messageBox.OfferMessage (
- messageHeader, messageValue, source, consumeToAccept);
- }
-
- public bool Post (TInput item)
- {
- return messageBox.OfferMessage (
- new DataflowMessageHeader (1), item, null, false)
- == DataflowMessageStatus.Accepted;
- }
-
- /// <summary>
- /// Processes one item from the queue if the action is synchronous.
- /// </summary>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue is empty.</returns>
- bool ProcessItem ()
- {
- TInput data;
- bool dequeued = messageQueue.TryTake (out data);
- if (dequeued)
- action (data);
- return dequeued;
- }
-
- /// <summary>
- /// Processes one item from the queue if the action is asynchronous.
- /// </summary>
- /// <param name="task">The Task that was returned by the synchronous part of the action.</param>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue was empty.</returns>
- bool AsyncProcessItem(out Task task)
- {
- TInput data;
- bool dequeued = messageQueue.TryTake (out data);
- if (dequeued)
- task = asyncAction (data);
- else
- task = null;
- return dequeued;
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get {
- return compHelper.Completion;
- }
- }
-
- public int InputCount {
- get {
- return messageQueue.Count;
- }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// AsyncExecutingMessageBox.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Message box for executing blocks with asynchrnous
- /// (<see cref="Task"/>-returning) actions.
- /// </summary>
- /// <typeparam name="TInput">Type of the item the block is processing.</typeparam>
- /// <typeparam name="TTask">Type of the Task the action is returning.</typeparam>
- class AsyncExecutingMessageBox<TInput, TTask>
- : ExecutingMessageBoxBase<TInput>
- where TTask : Task {
- /// <summary>
- /// Represents executing synchrnous part of the action.
- /// </summary>
- /// <param name="task">The Task that was returned by the synchronous part of the action.</param>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue was empty.</returns>
- public delegate bool AsyncProcessItem (out TTask task);
-
- readonly AsyncProcessItem processItem;
- readonly Action<TTask> processFinishedTask;
-
- public AsyncExecutingMessageBox (
- ITargetBlock<TInput> target, BlockingCollection<TInput> messageQueue,
- CompletionHelper compHelper, Func<bool> externalCompleteTester,
- AsyncProcessItem processItem, Action<TTask> processFinishedTask,
- Action outgoingQueueComplete, ExecutionDataflowBlockOptions options)
- : base (
- target, messageQueue, compHelper, externalCompleteTester,
- outgoingQueueComplete, options)
- {
- this.processItem = processItem;
- this.processFinishedTask = processFinishedTask;
- }
-
- /// <summary>
- /// Processes the input queue of the block.
- /// </summary>
- protected override void ProcessQueue ()
- {
- StartProcessQueue ();
-
- ProcessQueueWithoutStart ();
- }
-
- /// <summary>
- /// The part of <see cref="ProcessQueue"/> specific to asynchronous execution.
- /// Handles scheduling continuation on the Task returned by the block's action
- /// (or continuing synchrnously if possible).
- /// </summary>
- void ProcessQueueWithoutStart ()
- {
- // catch is needed here, if the Task-returning delegate throws exception itself
- try {
- int i = 0;
- while (CanRun (i)) {
- TTask task;
- if (!processItem (out task))
- break;
- if (task == null || task.IsCanceled
- || (task.IsCompleted && !task.IsFaulted)) {
- if (processFinishedTask != null)
- processFinishedTask (task);
- } else if (task.IsFaulted) {
- CompHelper.RequestFault (task.Exception, false);
- break;
- } else {
- task.ContinueWith (
- t => TaskFinished ((TTask)t), Options.TaskScheduler);
- return;
- }
- i++;
- }
- } catch (Exception e) {
- CompHelper.RequestFault (e, false);
- }
-
- FinishProcessQueue ();
- }
-
- /// <summary>
- /// Handles asynchronously finished Task, continues processing the queue.
- /// </summary>
- void TaskFinished (TTask task)
- {
- if (task.IsFaulted) {
- CompHelper.RequestFault (task.Exception, false);
- FinishProcessQueue ();
- return;
- }
-
- if (processFinishedTask != null)
- processFinishedTask (task);
-
- ProcessQueueWithoutStart ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// BatchBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class BatchBlock<T> : IPropagatorBlock<T, T[]>, IReceivableSourceBlock<T[]> {
- readonly CompletionHelper compHelper;
- readonly BlockingCollection<T> messageQueue = new BlockingCollection<T> ();
- readonly MessageBox<T> messageBox;
- readonly GroupingDataflowBlockOptions dataflowBlockOptions;
- readonly int batchSize;
- int batchCount;
- long numberOfGroups;
- SpinLock batchCountLock;
- readonly OutgoingQueue<T[]> outgoing;
- SpinLock batchLock;
- readonly AtomicBoolean nonGreedyProcessing = new AtomicBoolean ();
-
- public BatchBlock (int batchSize) : this (batchSize, GroupingDataflowBlockOptions.Default)
- {
- }
-
- public BatchBlock (int batchSize, GroupingDataflowBlockOptions dataflowBlockOptions)
- {
- if (batchSize <= 0)
- throw new ArgumentOutOfRangeException ("batchSize", batchSize,
- "The batchSize must be positive.");
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
- if (dataflowBlockOptions.BoundedCapacity != -1
- && batchSize > dataflowBlockOptions.BoundedCapacity)
- throw new ArgumentOutOfRangeException ("batchSize",
- "The batchSize must be smaller than the value of BoundedCapacity.");
-
- this.batchSize = batchSize;
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions);
-
- Action<bool> processQueue;
- Func<bool> canAccept;
- if (dataflowBlockOptions.MaxNumberOfGroups == -1) {
- processQueue = newItem => BatchProcess (newItem ? 1 : 0);
- canAccept = null;
- } else {
- processQueue = _ => BatchProcess ();
- canAccept = TryAdd;
- }
-
- this.messageBox = new PassingMessageBox<T> (this, messageQueue, compHelper,
- () => outgoing.IsCompleted, processQueue, dataflowBlockOptions,
- dataflowBlockOptions.Greedy, canAccept);
- this.outgoing = new OutgoingQueue<T[]> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions, batch => batch.Length);
- }
-
- DataflowMessageStatus ITargetBlock<T>.OfferMessage (
- DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source,
- bool consumeToAccept)
- {
- return messageBox.OfferMessage (
- messageHeader, messageValue, source, consumeToAccept);
- }
-
- public IDisposable LinkTo (ITargetBlock<T[]> target, DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- T[] ISourceBlock<T[]>.ConsumeMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<T[]> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- void ISourceBlock<T[]>.ReleaseReservation (
- DataflowMessageHeader messageHeader, ITargetBlock<T[]> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<T[]>.ReserveMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<T[]> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<T[]> filter, out T[] item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<T[]> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- /// <summary>
- /// Verifies whether <see cref="GroupingDataflowBlockOptions.MaxNumberOfGroups"/>
- /// has been reached. If it did, <see cref="Complete"/>s the block.
- /// </summary>
- void VerifyMaxNumberOfGroups ()
- {
- if (dataflowBlockOptions.MaxNumberOfGroups == -1)
- return;
-
- bool shouldComplete;
-
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- shouldComplete = numberOfGroups >= dataflowBlockOptions.MaxNumberOfGroups;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
-
- if (shouldComplete)
- Complete ();
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted, and increments a counter if it can.
- /// Only makes sense when <see cref="GroupingDataflowBlockOptions.MaxNumberOfGroups"/>
- /// is not unbounded.
- /// </summary>
- bool TryAdd ()
- {
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- if (numberOfGroups + batchCount / batchSize
- >= dataflowBlockOptions.MaxNumberOfGroups)
- return false;
-
- batchCount++;
- return true;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
- }
-
- public void TriggerBatch ()
- {
- if (dataflowBlockOptions.Greedy) {
- int earlyBatchSize;
-
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- if (batchCount == 0)
- return;
-
- earlyBatchSize = batchCount;
- batchCount = 0;
- numberOfGroups++;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
-
- MakeBatch (earlyBatchSize);
- } else {
- if (dataflowBlockOptions.BoundedCapacity == -1
- || outgoing.Count <= dataflowBlockOptions.BoundedCapacity)
- EnsureNonGreedyProcessing (true);
- }
- }
-
- /// <summary>
- /// Decides whether to create a new batch or not.
- /// </summary>
- /// <param name="addedItems">
- /// Number of newly added items. Used only with greedy processing.
- /// </param>
- void BatchProcess (int addedItems = 0)
- {
- if (dataflowBlockOptions.Greedy) {
- bool makeBatch = false;
-
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- batchCount += addedItems;
-
- if (batchCount >= batchSize) {
- batchCount -= batchSize;
- numberOfGroups++;
- makeBatch = true;
- }
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
-
- if (makeBatch)
- MakeBatch (batchSize);
- } else {
- if (ShouldProcessNonGreedy ())
- EnsureNonGreedyProcessing (false);
- }
- }
-
- /// <summary>
- /// Returns whether non-greedy creation of a batch should be started.
- /// </summary>
- bool ShouldProcessNonGreedy ()
- {
- // do we have enough items waiting and would the new batch fit?
- return messageBox.PostponedMessagesCount >= batchSize
- && (dataflowBlockOptions.BoundedCapacity == -1
- || outgoing.Count + batchSize <= dataflowBlockOptions.BoundedCapacity);
- }
-
- /// <summary>
- /// Creates a batch of the given size and adds the result to the output queue.
- /// </summary>
- void MakeBatch (int size)
- {
- T[] batch = new T[size];
-
- // lock is necessary here to make sure items are in the correct order
- bool taken = false;
- try {
- batchLock.Enter (ref taken);
-
- for (int i = 0; i < size; ++i)
- messageQueue.TryTake (out batch [i]);
- } finally {
- if (taken)
- batchLock.Exit ();
- }
-
- outgoing.AddData (batch);
-
- VerifyMaxNumberOfGroups ();
- }
-
- /// <summary>
- /// Starts non-greedy creation of batches, if one doesn't already run.
- /// </summary>
- /// <param name="manuallyTriggered">Whether the batch was triggered by <see cref="TriggerBatch"/>.</param>
- void EnsureNonGreedyProcessing (bool manuallyTriggered)
- {
- if (nonGreedyProcessing.TrySet ())
- Task.Factory.StartNew (() => NonGreedyProcess (manuallyTriggered),
- dataflowBlockOptions.CancellationToken,
- TaskCreationOptions.PreferFairness,
- dataflowBlockOptions.TaskScheduler);
- }
-
- /// <summary>
- /// Creates batches in non-greedy mode,
- /// making sure the whole batch is available by using reservations.
- /// </summary>
- /// <param name="manuallyTriggered">Whether the batch was triggered by <see cref="TriggerBatch"/>.</param>
- void NonGreedyProcess (bool manuallyTriggered)
- {
- bool first = true;
-
- do {
- var reservations =
- new List<Tuple<ISourceBlock<T>, DataflowMessageHeader>> ();
-
- int expectedReservationsCount = messageBox.PostponedMessagesCount;
-
- if (expectedReservationsCount == 0)
- break;
-
- bool gotReservation;
- do {
- var reservation = messageBox.ReserveMessage ();
- gotReservation = reservation != null;
- if (gotReservation)
- reservations.Add (reservation);
- } while (gotReservation && reservations.Count < batchSize);
-
- int expectedSize = manuallyTriggered && first
- ? Math.Min (expectedReservationsCount, batchSize)
- : batchSize;
-
- if (reservations.Count < expectedSize) {
- foreach (var reservation in reservations)
- messageBox.RelaseReservation (reservation);
-
- // some reservations failed, which most likely means the message
- // was consumed by someone else and a new one will be offered soon;
- // so postpone the batch, so that the other block has time to do that
- // (MS .Net does something like this too)
- if (manuallyTriggered && first) {
- Task.Factory.StartNew (() => NonGreedyProcess (true),
- dataflowBlockOptions.CancellationToken,
- TaskCreationOptions.PreferFairness,
- dataflowBlockOptions.TaskScheduler);
- return;
- }
- } else {
- T[] batch = new T[reservations.Count];
-
- for (int i = 0; i < reservations.Count; i++)
- batch [i] = messageBox.ConsumeReserved (reservations [i]);
-
- outgoing.AddData (batch);
-
- // non-greedy doesn't need lock
- numberOfGroups++;
-
- VerifyMaxNumberOfGroups ();
- }
-
- first = false;
- } while (ShouldProcessNonGreedy ());
-
- nonGreedyProcessing.Value = false;
- if (ShouldProcessNonGreedy ())
- EnsureNonGreedyProcessing (false);
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- TriggerBatch ();
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public int BatchSize {
- get { return batchSize; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// BatchedJoinBlock.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class BatchedJoinBlock<T1, T2> :
- IReceivableSourceBlock<Tuple<IList<T1>, IList<T2>>> {
- readonly GroupingDataflowBlockOptions options;
-
- readonly CompletionHelper completionHelper;
- readonly OutgoingQueue<Tuple<IList<T1>, IList<T2>>> outgoing;
- SpinLock batchLock;
-
- readonly JoinTarget<T1> target1;
- readonly JoinTarget<T2> target2;
-
- int batchCount;
- long numberOfGroups;
- SpinLock batchCountLock;
-
- public BatchedJoinBlock (int batchSize)
- : this (batchSize, GroupingDataflowBlockOptions.Default)
- {
- }
-
- public BatchedJoinBlock (int batchSize,
- GroupingDataflowBlockOptions dataflowBlockOptions)
- {
- if (batchSize <= 0)
- throw new ArgumentOutOfRangeException (
- "batchSize", batchSize, "The batchSize must be positive.");
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
- if (!dataflowBlockOptions.Greedy)
- throw new ArgumentException (
- "Greedy must be true for this dataflow block.", "dataflowBlockOptions");
- if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded)
- throw new ArgumentException (
- "BoundedCapacity must be Unbounded or -1 for this dataflow block.",
- "dataflowBlockOptions");
-
- BatchSize = batchSize;
- options = dataflowBlockOptions;
- completionHelper = CompletionHelper.GetNew (dataflowBlockOptions);
-
- target1 = new JoinTarget<T1> (
- this, SignalTarget, completionHelper, () => outgoing.IsCompleted,
- dataflowBlockOptions, true, TryAdd);
- target2 = new JoinTarget<T2> (
- this, SignalTarget, completionHelper, () => outgoing.IsCompleted,
- dataflowBlockOptions, true, TryAdd);
-
- outgoing = new OutgoingQueue<Tuple<IList<T1>, IList<T2>>> (
- this, completionHelper,
- () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted,
- _ =>
- {
- target1.DecreaseCount ();
- target2.DecreaseCount ();
- }, options);
- }
-
- public int BatchSize { get; private set; }
-
- public ITargetBlock<T1> Target1 {
- get { return target1; }
- }
-
- public ITargetBlock<T2> Target2 {
- get { return target2; }
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted, and increments a counter if it can.
- /// </summary>
- bool TryAdd ()
- {
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- if (options.MaxNumberOfGroups != -1
- && numberOfGroups + batchCount / BatchSize >= options.MaxNumberOfGroups)
- return false;
-
- batchCount++;
- return true;
- } finally {
- if (lockTaken)
- batchCountLock.Exit();
- }
- }
-
- /// <summary>
- /// Decides whether to create a new batch or not.
- /// </summary>
- void SignalTarget ()
- {
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- if (batchCount < BatchSize)
- return;
-
- batchCount -= BatchSize;
- numberOfGroups++;
- } finally {
- if (lockTaken)
- batchCountLock.Exit();
- }
-
- MakeBatch (BatchSize);
- }
-
- /// <summary>
- /// Creates a batch of the given size and adds the resulting batch to the output queue.
- /// </summary>
- void MakeBatch (int batchSize)
- {
- if (batchSize == 0)
- return;
-
- var list1 = new List<T1> ();
- var list2 = new List<T2> ();
-
- // lock is necessary here to make sure items are in the correct order
- bool taken = false;
- try {
- batchLock.Enter (ref taken);
-
- int i = 0;
-
- T1 item1;
- while (i < batchSize && target1.Buffer.TryTake (out item1)) {
- list1.Add (item1);
- i++;
- }
-
- T2 item2;
- while (i < batchSize && target2.Buffer.TryTake (out item2)) {
- list2.Add (item2);
- i++;
- }
-
- if (i < batchSize)
- throw new InvalidOperationException("Unexpected count of items.");
- } finally {
- if (taken)
- batchLock.Exit ();
- }
-
- var batch = Tuple.Create<IList<T1>, IList<T2>> (list1, list2);
-
- outgoing.AddData (batch);
-
- VerifyMaxNumberOfGroups ();
- }
-
- /// <summary>
- /// Verifies whether <see cref="GroupingDataflowBlockOptions.MaxNumberOfGroups"/>
- /// has been reached. If it did, <see cref="Complete"/>s the block.
- /// </summary>
- void VerifyMaxNumberOfGroups ()
- {
- if (options.MaxNumberOfGroups == -1)
- return;
-
- bool shouldComplete;
-
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- shouldComplete = numberOfGroups >= options.MaxNumberOfGroups;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
-
- if (shouldComplete)
- Complete ();
- }
-
- public Task Completion {
- get { return completionHelper.Completion; }
- }
-
- public void Complete ()
- {
- target1.Complete ();
- target2.Complete ();
- MakeBatch (batchCount);
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- completionHelper.RequestFault (exception);
- }
-
- Tuple<IList<T1>, IList<T2>> ISourceBlock<Tuple<IList<T1>, IList<T2>>>.ConsumeMessage (
- DataflowMessageHeader messageHeader,
- ITargetBlock<Tuple<IList<T1>, IList<T2>>> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- public IDisposable LinkTo (ITargetBlock<Tuple<IList<T1>, IList<T2>>> target,
- DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget(target, linkOptions);
- }
-
- void ISourceBlock<Tuple<IList<T1>, IList<T2>>>.ReleaseReservation (
- DataflowMessageHeader messageHeader,
- ITargetBlock<Tuple<IList<T1>, IList<T2>>> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<Tuple<IList<T1>, IList<T2>>>.ReserveMessage (
- DataflowMessageHeader messageHeader,
- ITargetBlock<Tuple<IList<T1>, IList<T2>>> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<Tuple<IList<T1>, IList<T2>>> filter,
- out Tuple<IList<T1>, IList<T2>> item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<Tuple<IList<T1>, IList<T2>>> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, options);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// BatchedJoinBlock.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class BatchedJoinBlock<T1, T2, T3> :
- IReceivableSourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> {
- readonly GroupingDataflowBlockOptions options;
-
- readonly CompletionHelper completionHelper;
- readonly OutgoingQueue<Tuple<IList<T1>, IList<T2>, IList<T3>>> outgoing;
- SpinLock batchLock;
-
- readonly JoinTarget<T1> target1;
- readonly JoinTarget<T2> target2;
- readonly JoinTarget<T3> target3;
-
- int batchCount;
- long numberOfGroups;
- SpinLock batchCountLock;
-
- public BatchedJoinBlock (int batchSize)
- : this (batchSize, GroupingDataflowBlockOptions.Default)
- {
- }
-
- public BatchedJoinBlock (int batchSize,
- GroupingDataflowBlockOptions dataflowBlockOptions)
- {
- if (batchSize <= 0)
- throw new ArgumentOutOfRangeException (
- "batchSize", batchSize, "The batchSize must be positive.");
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
- if (!dataflowBlockOptions.Greedy)
- throw new ArgumentException (
- "Greedy must be true for this dataflow block.", "dataflowBlockOptions");
- if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded)
- throw new ArgumentException (
- "BoundedCapacity must be Unbounded or -1 for this dataflow block.",
- "dataflowBlockOptions");
-
- BatchSize = batchSize;
- options = dataflowBlockOptions;
- completionHelper = CompletionHelper.GetNew (options);
-
- target1 = new JoinTarget<T1> (
- this, SignalTarget, completionHelper, () => outgoing.IsCompleted,
- dataflowBlockOptions, true, TryAdd);
- target2 = new JoinTarget<T2> (
- this, SignalTarget, completionHelper, () => outgoing.IsCompleted,
- dataflowBlockOptions, true, TryAdd);
- target3 = new JoinTarget<T3> (
- this, SignalTarget, completionHelper, () => outgoing.IsCompleted,
- dataflowBlockOptions, true, TryAdd);
-
- outgoing = new OutgoingQueue<Tuple<IList<T1>, IList<T2>, IList<T3>>> (
- this, completionHelper,
- () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted
- || target3.Buffer.IsCompleted,
- _ =>
- {
- target1.DecreaseCount ();
- target2.DecreaseCount ();
- target3.DecreaseCount ();
- }, options);
- }
-
- public int BatchSize { get; private set; }
-
- public ITargetBlock<T1> Target1 {
- get { return target1; }
- }
-
- public ITargetBlock<T2> Target2 {
- get { return target2; }
- }
-
- public ITargetBlock<T3> Target3 {
- get { return target3; }
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted, and increments a counter if it can.
- /// </summary>
- bool TryAdd ()
- {
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- if (options.MaxNumberOfGroups != -1
- && numberOfGroups + batchCount / BatchSize >= options.MaxNumberOfGroups)
- return false;
-
- batchCount++;
- return true;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
- }
-
- /// <summary>
- /// Decides whether to create a new batch or not.
- /// </summary>
- void SignalTarget ()
- {
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- if (batchCount < BatchSize)
- return;
-
- batchCount -= BatchSize;
- numberOfGroups++;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
-
- MakeBatch (BatchSize);
- }
-
- /// <summary>
- /// Creates a batch of the given size and adds the resulting batch to the output queue.
- /// </summary>
- void MakeBatch (int batchSize)
- {
- if (batchSize == 0)
- return;
-
- var list1 = new List<T1> ();
- var list2 = new List<T2> ();
- var list3 = new List<T3> ();
-
- // lock is necessary here to make sure items are in the correct order
- bool taken = false;
- try {
- batchLock.Enter (ref taken);
-
- int i = 0;
-
- T1 item1;
- while (i < batchSize && target1.Buffer.TryTake (out item1)) {
- list1.Add (item1);
- i++;
- }
-
- T2 item2;
- while (i < batchSize && target2.Buffer.TryTake (out item2)) {
- list2.Add (item2);
- i++;
- }
-
- T3 item3;
- while (i < batchSize && target3.Buffer.TryTake (out item3)) {
- list3.Add (item3);
- i++;
- }
-
- if (i < batchSize)
- throw new InvalidOperationException ("Unexpected count of items.");
- } finally {
- if (taken)
- batchLock.Exit ();
- }
-
- var batch = Tuple.Create<IList<T1>, IList<T2>, IList<T3>> (list1, list2,
- list3);
-
- outgoing.AddData (batch);
-
- VerifyMaxNumberOfGroups ();
- }
-
- /// <summary>
- /// Verifies whether <see cref="GroupingDataflowBlockOptions.MaxNumberOfGroups"/>
- /// has been reached. If it did, <see cref="Complete"/>s the block.
- /// </summary>
- void VerifyMaxNumberOfGroups ()
- {
- if (options.MaxNumberOfGroups == -1)
- return;
-
- bool shouldComplete;
-
- bool lockTaken = false;
- try {
- batchCountLock.Enter (ref lockTaken);
-
- shouldComplete = numberOfGroups >= options.MaxNumberOfGroups;
- } finally {
- if (lockTaken)
- batchCountLock.Exit ();
- }
-
- if (shouldComplete)
- Complete ();
- }
-
- public Task Completion
- {
- get { return completionHelper.Completion; }
- }
-
- public void Complete ()
- {
- target1.Complete ();
- target2.Complete ();
- target3.Complete ();
- MakeBatch (batchCount);
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- completionHelper.RequestFault (exception);
- }
-
- Tuple<IList<T1>, IList<T2>, IList<T3>>
- ISourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>.ConsumeMessage (
- DataflowMessageHeader messageHeader,
- ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- public IDisposable LinkTo (
- ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target,
- DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- void ISourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>.ReleaseReservation (
- DataflowMessageHeader messageHeader,
- ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>>.ReserveMessage (
- DataflowMessageHeader messageHeader,
- ITargetBlock<Tuple<IList<T1>, IList<T2>, IList<T3>>> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public bool TryReceive (
- Predicate<Tuple<IList<T1>, IList<T2>, IList<T3>>> filter,
- out Tuple<IList<T1>, IList<T2>, IList<T3>> item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (
- out IList<Tuple<IList<T1>, IList<T2>, IList<T3>>> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, options);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// BroadcastBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class BroadcastBlock<T> : IPropagatorBlock<T, T>, IReceivableSourceBlock<T> {
- readonly CompletionHelper compHelper;
- readonly BlockingCollection<T> messageQueue = new BlockingCollection<T> ();
- readonly MessageBox<T> messageBox;
- readonly DataflowBlockOptions dataflowBlockOptions;
- readonly Func<T, T> cloningFunction;
- readonly BroadcastOutgoingQueue<T> outgoing;
-
- public BroadcastBlock (Func<T, T> cloningFunction)
- : this (cloningFunction, DataflowBlockOptions.Default)
- {
- }
-
- public BroadcastBlock (Func<T, T> cloningFunction,
- DataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.cloningFunction = cloningFunction;
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions);
- this.messageBox = new PassingMessageBox<T> (this, messageQueue, compHelper,
- () => outgoing.IsCompleted, _ => BroadcastProcess (), dataflowBlockOptions);
- this.outgoing = new BroadcastOutgoingQueue<T> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions, cloningFunction != null);
- }
-
- DataflowMessageStatus ITargetBlock<T>.OfferMessage (
- DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source,
- bool consumeToAccept)
- {
- return messageBox.OfferMessage (messageHeader, messageValue, source,
- consumeToAccept);
- }
-
- public IDisposable LinkTo (ITargetBlock<T> target, DataflowLinkOptions linkOptions)
- {
- if (linkOptions == null)
- throw new ArgumentNullException("linkOptions");
-
- return outgoing.AddTarget (target, linkOptions);
- }
-
- T ISourceBlock<T>.ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target,
- out bool messageConsumed)
- {
- T message = outgoing.ConsumeMessage (
- messageHeader, target, out messageConsumed);
- if (messageConsumed && cloningFunction != null)
- message = cloningFunction (message);
- return message;
- }
-
- bool ISourceBlock<T>.ReserveMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- void ISourceBlock<T>.ReleaseReservation (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<T> filter, out T item)
- {
- var received = outgoing.TryReceive (filter, out item);
- if (received && cloningFunction != null)
- item = cloningFunction (item);
- return received;
- }
-
- bool IReceivableSourceBlock<T>.TryReceiveAll (out IList<T> items)
- {
- T item;
- if (!TryReceive (null, out item)) {
- items = null;
- return false;
- }
-
- items = new[] { item };
- return true;
- }
-
- /// <summary>
- /// Moves items from the input queue to the output queue.
- /// </summary>
- void BroadcastProcess ()
- {
- T item;
- while (messageQueue.TryTake (out item))
- outgoing.AddData (item);
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// BroadcastOutgoingQueue.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Version of <see cref="OutgoingQueueBase{T}"/> for broadcast blocks.
- /// </summary>
- class BroadcastOutgoingQueue<T> : OutgoingQueueBase<T> {
- volatile bool hasCurrentItem;
- // don't use directly, only through CurrentItem (and carefully)
- T currentItem;
- SpinLock currentItemLock = new SpinLock();
-
- readonly BroadcastTargetCollection<T> targets;
-
- protected override TargetCollectionBase<T> Targets {
- get { return targets; }
- }
-
- readonly ConcurrentDictionary<Tuple<DataflowMessageHeader, ITargetBlock<T>>, T>
- reservedMessages =
- new ConcurrentDictionary<Tuple<DataflowMessageHeader, ITargetBlock<T>>, T>();
-
- public BroadcastOutgoingQueue (
- ISourceBlock<T> block, CompletionHelper compHelper,
- Func<bool> externalCompleteTester, Action<int> decreaseItemsCount,
- DataflowBlockOptions options, bool hasCloner)
- : base (compHelper, externalCompleteTester, decreaseItemsCount, options)
- {
- targets = new BroadcastTargetCollection<T> (block, hasCloner);
- }
-
- /// <summary>
- /// The current item that is to be sent to taget blocks.
- /// </summary>
- T CurrentItem {
- get {
- T item;
- bool lockTaken = false;
- try {
- currentItemLock.Enter (ref lockTaken);
- item = currentItem;
- } finally {
- if (lockTaken)
- currentItemLock.Exit ();
- }
- return item;
- }
- set {
- hasCurrentItem = true;
-
- bool lockTaken = false;
- try {
- currentItemLock.Enter (ref lockTaken);
- currentItem = value;
- } finally {
- if (lockTaken)
- currentItemLock.Exit ();
- }
- }
- }
-
- /// <summary>
- /// Takes an item from the queue and sets it as <see cref="CurrentItem"/>.
- /// </summary>
- public void DequeueItem ()
- {
- T item;
- if (Outgoing.TryTake (out item)) {
- DecreaseCounts (item);
- targets.SetCurrentItem (item);
-
- CurrentItem = item;
- }
- }
-
- /// <summary>
- /// Manages sending items to the target blocks.
- /// </summary>
- protected override void Process ()
- {
- do {
- ForceProcessing = false;
-
- DequeueItem ();
-
- targets.OfferItemToTargets ();
- } while (!Store.IsEmpty || targets.NeedsProcessing);
-
- IsProcessing.Value = false;
-
- // to guard against race condition
- if (ForceProcessing)
- EnsureProcessing ();
-
- VerifyCompleteness ();
- }
-
- public T ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target, out bool messageConsumed)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (target == null)
- throw new ArgumentNullException("target");
-
- T item;
- if (reservedMessages.TryRemove (Tuple.Create (messageHeader, target), out item)) {
- messageConsumed = true;
- return item;
- }
-
- // if we first retrieve CurrentItem and then check the header,
- // there will be no race condition
-
- item = CurrentItem;
-
- if (!targets.VerifyHeader (messageHeader)) {
- targets.UnpostponeTargetNotConsumed (target);
-
- messageConsumed = false;
- return default(T);
- }
-
- targets.UnpostponeTargetConsumed (target, messageHeader);
- EnsureProcessing ();
-
- messageConsumed = true;
- return item;
- }
-
- public bool ReserveMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (target == null)
- throw new ArgumentNullException("target");
-
- T item = CurrentItem;
-
- if (!targets.VerifyHeader (messageHeader)) {
- targets.UnpostponeTargetNotConsumed (target);
- EnsureProcessing ();
- return false;
- }
-
- targets.ReserveTarget (target);
- reservedMessages [Tuple.Create (messageHeader, target)] = item;
- return true;
- }
-
- public void ReleaseReservation (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (target == null)
- throw new ArgumentNullException("target");
-
- T item;
- if (!reservedMessages.TryRemove (Tuple.Create (messageHeader, target), out item))
- throw new InvalidOperationException (
- "The target did not have the message reserved.");
-
- targets.UnpostponeTargetNotConsumed (target);
- EnsureProcessing ();
- }
-
- public bool TryReceive (Predicate<T> filter, out T retrievedItem)
- {
- retrievedItem = default(T);
-
- if (!hasCurrentItem) {
- return false;
- }
-
- T item = CurrentItem;
-
- if (filter == null || filter(item)) {
- retrievedItem = item;
- return true;
- }
-
- return false;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// BufferBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class BufferBlock<T> : IPropagatorBlock<T, T>, IReceivableSourceBlock<T> {
- readonly DataflowBlockOptions dataflowBlockOptions;
- readonly CompletionHelper compHelper;
- readonly MessageBox<T> messageBox;
- readonly OutgoingQueue<T> outgoing;
- readonly BlockingCollection<T> messageQueue = new BlockingCollection<T> ();
-
- public BufferBlock () : this (DataflowBlockOptions.Default)
- {
- }
-
- public BufferBlock (DataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions);
- this.messageBox = new PassingMessageBox<T> (this, messageQueue, compHelper,
- () => outgoing.IsCompleted, _ => ProcessQueue (), dataflowBlockOptions);
- this.outgoing = new OutgoingQueue<T> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions);
- }
-
- DataflowMessageStatus ITargetBlock<T>.OfferMessage (
- DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source,
- bool consumeToAccept)
- {
- return messageBox.OfferMessage (messageHeader, messageValue, source, consumeToAccept);
- }
-
- public IDisposable LinkTo (ITargetBlock<T> target, DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- T ISourceBlock<T>.ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- bool ISourceBlock<T>.ReserveMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- void ISourceBlock<T>.ReleaseReservation (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<T> filter, out T item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<T> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- /// <summary>
- /// Moves items from the input queue to the output queue.
- /// </summary>
- void ProcessQueue ()
- {
- T item;
- while (messageQueue.TryTake (out item))
- outgoing.AddData (item);
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get {
- return compHelper.Completion;
- }
- }
-
- public int Count {
- get {
- return outgoing.Count;
- }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
-
+++ /dev/null
-// JoinBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Block used in all versions of <see cref="DataflowBlock.Choose"/>.
- /// </summary>
- class ChooserBlock<T1, T2, T3> {
- /// <summary>
- /// Target for one of the sources to choose from.
- /// </summary>
- class ChooseTarget<TMessage> : ITargetBlock<TMessage> {
- readonly ChooserBlock<T1, T2, T3> chooserBlock;
- readonly int index;
- readonly Action<TMessage> action;
-
- public ChooseTarget (ChooserBlock<T1, T2, T3> chooserBlock,
- int index, Action<TMessage> action)
- {
- this.chooserBlock = chooserBlock;
- this.index = index;
- this.action = action;
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TMessage messageValue,
- ISourceBlock<TMessage> source, bool consumeToAccept)
- {
- if (!chooserBlock.canAccept)
- return DataflowMessageStatus.DecliningPermanently;
-
- bool lockTaken = false;
- try {
- chooserBlock.messageLock.Enter (ref lockTaken);
- if (!chooserBlock.canAccept)
- return DataflowMessageStatus.DecliningPermanently;
-
- if (consumeToAccept) {
- bool consummed;
- messageValue = source.ConsumeMessage (messageHeader, this, out consummed);
- if (!consummed)
- return DataflowMessageStatus.NotAvailable;
- }
-
- chooserBlock.canAccept = false;
- } finally {
- if (lockTaken)
- chooserBlock.messageLock.Exit ();
- }
-
- chooserBlock.MessageArrived (index, action, messageValue);
- return DataflowMessageStatus.Accepted;
- }
-
- public Task Completion {
- get { return null; }
- }
-
- public void Complete ()
- {
- }
-
- public void Fault (Exception exception)
- {
- }
- }
-
- readonly TaskCompletionSource<int> completion = new TaskCompletionSource<int> ();
-
- SpinLock messageLock;
- bool canAccept = true;
-
- public ChooserBlock (
- Action<T1> action1, Action<T2> action2, Action<T3> action3,
- DataflowBlockOptions dataflowBlockOptions)
- {
- Target1 = new ChooseTarget<T1> (this, 0, action1);
- Target2 = new ChooseTarget<T2> (this, 1, action2);
- if (action3 != null)
- Target3 = new ChooseTarget<T3> (this, 2, action3);
-
- if (dataflowBlockOptions.CancellationToken != CancellationToken.None)
- dataflowBlockOptions.CancellationToken.Register (Cancelled);
- }
-
- /// <summary>
- /// Causes cancellation of <see cref="Completion"/>.
- /// If a message is already being consumed (and the consumsing succeeds)
- /// or if its action is being invoked, the Task is not cancelled.
- /// </summary>
- void Cancelled ()
- {
- if (!canAccept)
- return;
-
- bool lockTaken = false;
- try {
- messageLock.Enter (ref lockTaken);
- if (!canAccept)
- return;
-
- completion.SetCanceled ();
-
- canAccept = false;
- } finally {
- if (lockTaken)
- messageLock.Exit ();
- }
- }
-
- /// <summary>
- /// Called when all sources have completed,
- /// causes cancellation of <see cref="Completion"/>.
- /// </summary>
- public void AllSourcesCompleted ()
- {
- Cancelled ();
- }
-
- /// <summary>
- /// Called when message has arrived (and was consumed, if necessary).
- /// This method can be called only once in the lifetime of this object.
- /// </summary>
- void MessageArrived<TMessage> (
- int index, Action<TMessage> action, TMessage value)
- {
- try {
- action (value);
- completion.SetResult (index);
- } catch (Exception e) {
- completion.SetException (e);
- }
- }
-
- /// <summary>
- /// Target block for the first source block.
- /// </summary>
- public ITargetBlock<T1> Target1 { get; private set; }
-
- /// <summary>
- /// Target block for the second source block.
- /// </summary>
- public ITargetBlock<T2> Target2 { get; private set; }
-
- /// <summary>
- /// Target block for the third source block.
- /// Is <c>null</c> if there are only two actions.
- /// </summary>
- public ITargetBlock<T3> Target3 { get; private set; }
-
- /// <summary>
- /// Task that signifies that an item was accepted and
- /// its action has been called.
- /// </summary>
- public Task<int> Completion {
- get { return completion.Task; }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// CompletionHelper.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Used to implement Dataflow completion tracking,
- /// that is the Completion property, Complete/Fault method combo
- /// and the CancellationToken option.
- /// </summary>
- class CompletionHelper {
- readonly TaskCompletionSource<object> source =
- new TaskCompletionSource<object> ();
-
- readonly AtomicBoolean canFaultOrCancelImmediatelly =
- new AtomicBoolean { Value = true };
- readonly AtomicBoolean requestedFaultOrCancel =
- new AtomicBoolean { Value = false };
-
- readonly ConcurrentQueue<Tuple<Exception, bool>> requestedExceptions =
- new ConcurrentQueue<Tuple<Exception, bool>> ();
-
- public CompletionHelper (DataflowBlockOptions options)
- {
- if (options != null && options.CancellationToken != CancellationToken.None)
- options.CancellationToken.Register (RequestCancel);
- }
-
- [Obsolete ("Use ctor")]
- public static CompletionHelper GetNew (DataflowBlockOptions options)
- {
- return new CompletionHelper (options);
- }
-
- public Task Completion {
- get { return source.Task; }
- }
-
- /// <summary>
- /// Whether <see cref="Completion"/> can be faulted or cancelled immediatelly.
- /// It can't for example when a block is currently executing user action.
- /// In that case, the fault (or cancellation) is queued,
- /// and is actually acted upon when this property is set back to <c>true</c>.
- /// </summary>
- public bool CanFaultOrCancelImmediatelly {
- get { return canFaultOrCancelImmediatelly.Value; }
- set {
- if (value) {
- if (canFaultOrCancelImmediatelly.TrySet () && requestedFaultOrCancel.Value) {
- bool canAllBeIgnored = requestedExceptions.All (t => t.Item2);
- if (canAllBeIgnored) {
- Tuple<Exception, bool> tuple;
- requestedExceptions.TryDequeue (out tuple);
- var exception = tuple.Item1;
- if (exception == null)
- Cancel ();
- else
- Fault (exception);
- } else {
- Tuple<Exception, bool> tuple;
- bool first = true;
- var exceptions = new List<Exception> (requestedExceptions.Count);
- while (requestedExceptions.TryDequeue (out tuple)) {
- var exception = tuple.Item1;
- bool canBeIgnored = tuple.Item2;
- if (first || !canBeIgnored) {
- if (exception != null)
- exceptions.Add (exception);
- }
- first = false;
- }
- Fault (exceptions);
- }
- }
- } else
- canFaultOrCancelImmediatelly.Value = false;
- }
- }
-
- /// <summary>
- /// Whether the block can act as if it's not completed
- /// (accept new items, start executing user action).
- /// </summary>
- public bool CanRun {
- get { return !Completion.IsCompleted && !requestedFaultOrCancel.Value; }
- }
-
- /// <summary>
- /// Sets the block as completed.
- /// Should be called only when the block is really completed
- /// (e.g. the output queue is empty) and not right after
- /// the user calls <see cref="IDataflowBlock.Complete"/>.
- /// </summary>
- public void Complete ()
- {
- source.TrySetResult (null);
- }
-
- /// <summary>
- /// Requests faulting of the block using a given exception.
- /// If the block can't be faulted immediatelly (see <see cref="CanFaultOrCancelImmediatelly"/>),
- /// the exception will be queued, and the block will fault as soon as it can.
- /// </summary>
- /// <param name="exception">The exception that is the cause of the fault.</param>
- /// <param name="canBeIgnored">Can this exception be ignored, if there are more exceptions?</param>
- /// <remarks>
- /// When calling <see cref="IDataflowBlock.Fault"/> repeatedly, only the first exception counts,
- /// even in the cases where the block can't be faulted immediatelly.
- /// But exceptions from user actions in execution blocks count always,
- /// which is the reason for the <paramref name="canBeIgnored"/> parameter.
- /// </remarks>
- public void RequestFault (Exception exception, bool canBeIgnored = true)
- {
- if (exception == null)
- throw new ArgumentNullException ("exception");
-
- if (CanFaultOrCancelImmediatelly)
- Fault (exception);
- else {
- // still need to store canBeIgnored, if we don't want to add locking here
- if (!canBeIgnored || requestedExceptions.Count == 0)
- requestedExceptions.Enqueue (Tuple.Create (exception, canBeIgnored));
- requestedFaultOrCancel.Value = true;
- }
- }
-
- /// <summary>
- /// Actually faults the block with a single exception.
- /// </summary>
- /// <remarks>
- /// Should be only called when <see cref="CanFaultOrCancelImmediatelly"/> is <c>true</c>.
- /// </remarks>
- void Fault (Exception exception)
- {
- source.TrySetException (exception);
- }
-
- /// <summary>
- /// Actually faults the block with a multiple exceptions.
- /// </summary>
- /// <remarks>
- /// Should be only called when <see cref="CanFaultOrCancelImmediatelly"/> is <c>true</c>.
- /// </remarks>
- void Fault (IEnumerable<Exception> exceptions)
- {
- source.TrySetException (exceptions);
- }
-
- /// <summary>
- /// Requests cancellation of the block.
- /// If the block can't be cancelled immediatelly (see <see cref="CanFaultOrCancelImmediatelly"/>),
- /// the cancellation will be queued, and the block will cancel as soon as it can.
- /// </summary>
- void RequestCancel ()
- {
- if (CanFaultOrCancelImmediatelly)
- Cancel ();
- else {
- if (requestedExceptions.Count == 0)
- requestedExceptions.Enqueue (Tuple.Create<Exception, bool> (null, true));
- requestedFaultOrCancel.Value = true;
- }
- }
-
- /// <summary>
- /// Actually cancels the block.
- /// </summary>
- /// <remarks>
- /// Should be only called when <see cref="CanFaultOrCancelImmediatelly"/> is <c>true</c>.
- /// </remarks>
- void Cancel ()
- {
- source.TrySetCanceled ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// DataflowBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public static class DataflowBlock {
- public static IObservable<TOutput> AsObservable<TOutput> (this ISourceBlock<TOutput> source)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
-
- return new ObservableDataflowBlock<TOutput> (source);
- }
-
- public static IObserver<TInput> AsObserver<TInput> (this ITargetBlock<TInput> target)
- {
- if (target == null)
- throw new ArgumentNullException ("target");
-
- return new ObserverDataflowBlock<TInput> (target);
- }
-
- public static Task<int> Choose<T1, T2> (
- ISourceBlock<T1> source1, Action<T1> action1,
- ISourceBlock<T2> source2, Action<T2> action2)
- {
- return Choose (source1, action1, source2, action2,
- DataflowBlockOptions.Default);
- }
-
- public static Task<int> Choose<T1, T2> (
- ISourceBlock<T1> source1, Action<T1> action1,
- ISourceBlock<T2> source2, Action<T2> action2,
- DataflowBlockOptions dataflowBlockOptions)
- {
- if (source1 == null)
- throw new ArgumentNullException ("source1");
- if (source2 == null)
- throw new ArgumentNullException ("source2");
- if (action1 == null)
- throw new ArgumentNullException ("action1");
- if (action2 == null)
- throw new ArgumentNullException ("action2");
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- var chooser = new ChooserBlock<T1, T2, object> (action1, action2, null, dataflowBlockOptions);
- source1.LinkTo (chooser.Target1);
- source2.LinkTo (chooser.Target2);
-
- Task.WhenAll (source1.Completion, source2.Completion)
- .ContinueWith (_ => chooser.AllSourcesCompleted ());
-
- return chooser.Completion;
- }
-
- public static Task<int> Choose<T1, T2, T3> (
- ISourceBlock<T1> source1, Action<T1> action1,
- ISourceBlock<T2> source2, Action<T2> action2,
- ISourceBlock<T3> source3, Action<T3> action3)
- {
- return Choose (source1, action1, source2, action2, source3, action3,
- DataflowBlockOptions.Default);
- }
-
- public static Task<int> Choose<T1, T2, T3> (
- ISourceBlock<T1> source1, Action<T1> action1,
- ISourceBlock<T2> source2, Action<T2> action2,
- ISourceBlock<T3> source3, Action<T3> action3,
- DataflowBlockOptions dataflowBlockOptions)
- {
- if (source1 == null)
- throw new ArgumentNullException ("source1");
- if (source2 == null)
- throw new ArgumentNullException ("source2");
- if (source3 == null)
- throw new ArgumentNullException ("source3");
- if (action1 == null)
- throw new ArgumentNullException ("action1");
- if (action2 == null)
- throw new ArgumentNullException ("action2");
- if (action3 == null)
- throw new ArgumentNullException ("action3");
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- var chooser = new ChooserBlock<T1, T2, T3> (action1, action2, action3, dataflowBlockOptions);
- source1.LinkTo (chooser.Target1);
- source2.LinkTo (chooser.Target2);
- source3.LinkTo (chooser.Target3);
-
- Task.WhenAll (source1.Completion, source2.Completion, source3.Completion)
- .ContinueWith (_ => chooser.AllSourcesCompleted ());
-
- return chooser.Completion;
- }
-
- public static IPropagatorBlock<TInput, TOutput> Encapsulate<TInput, TOutput> (
- ITargetBlock<TInput> target, ISourceBlock<TOutput> source)
- {
- return new PropagatorWrapperBlock<TInput, TOutput> (target, source);
- }
-
- public static IDisposable LinkTo<TOutput> (this ISourceBlock<TOutput> source, ITargetBlock<TOutput> target)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
-
- return source.LinkTo (target, DataflowLinkOptions.Default);
- }
-
- public static IDisposable LinkTo<TOutput> (
- this ISourceBlock<TOutput> source, ITargetBlock<TOutput> target,
- Predicate<TOutput> predicate)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
-
- return source.LinkTo (target, DataflowLinkOptions.Default, predicate);
- }
-
- public static IDisposable LinkTo<TOutput> (
- this ISourceBlock<TOutput> source, ITargetBlock<TOutput> target,
- DataflowLinkOptions linkOptions, Predicate<TOutput> predicate)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
- if (predicate == null)
- throw new ArgumentNullException ("predicate");
- if (target == null)
- throw new ArgumentNullException ("target");
-
- var predicateBlock = new PredicateBlock<TOutput> (source, target, predicate);
-
- return source.LinkTo (predicateBlock, linkOptions);
- }
-
- public static Task<bool> OutputAvailableAsync<TOutput> (
- this ISourceBlock<TOutput> source)
- {
- return OutputAvailableAsync (source, CancellationToken.None);
- }
-
- public static Task<bool> OutputAvailableAsync<TOutput> (
- this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
-
- cancellationToken.ThrowIfCancellationRequested ();
-
- if (source.Completion.IsCompleted || source.Completion.IsCanceled
- || source.Completion.IsFaulted)
- return Task.FromResult (false);
-
- var block = new OutputAvailableBlock<TOutput> ();
- var bridge = source.LinkTo (block,
- new DataflowLinkOptions { PropagateCompletion = true });
- return block.AsyncGet (bridge, cancellationToken);
- }
-
- public static bool Post<TInput> (this ITargetBlock<TInput> target, TInput item)
- {
- if (target == null)
- throw new ArgumentNullException ("target");
-
- return target.OfferMessage (new DataflowMessageHeader(1), item, null, false)
- == DataflowMessageStatus.Accepted;
- }
-
- public static TOutput Receive<TOutput> (this ISourceBlock<TOutput> source)
- {
- return Receive (source, TimeSpan.FromMilliseconds (-1), CancellationToken.None);
- }
-
- public static TOutput Receive<TOutput> (this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
- {
- return Receive (source, TimeSpan.FromMilliseconds (-1), cancellationToken);
- }
-
- public static TOutput Receive<TOutput> (this ISourceBlock<TOutput> source, TimeSpan timeout)
- {
- return Receive (source, timeout, CancellationToken.None);
- }
-
- public static TOutput Receive<TOutput> (
- this ISourceBlock<TOutput> source, TimeSpan timeout,
- CancellationToken cancellationToken)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
- if (timeout.TotalMilliseconds < -1)
- throw new ArgumentOutOfRangeException ("timeout");
- if (timeout.TotalMilliseconds > int.MaxValue)
- throw new ArgumentOutOfRangeException ("timeout");
-
- cancellationToken.ThrowIfCancellationRequested ();
-
- TOutput item;
- var receivableSource = source as IReceivableSourceBlock<TOutput>;
- if (receivableSource != null && receivableSource.TryReceive (null, out item))
- return item;
-
- if (source.Completion.IsCompleted || source.Completion.IsCanceled
- || source.Completion.IsFaulted)
- throw new InvalidOperationException (
- "No item could be received from the source.");
-
- int timeoutMilliseconds = (int)timeout.TotalMilliseconds;
- var block = new ReceiveBlock<TOutput> (cancellationToken, timeoutMilliseconds);
- var bridge = source.LinkTo (block,
- new DataflowLinkOptions { PropagateCompletion = true });
- return block.WaitAndGet (bridge);
- }
-
- public static Task<TOutput> ReceiveAsync<TOutput> (this ISourceBlock<TOutput> source)
- {
- return ReceiveAsync (source, TimeSpan.FromMilliseconds (-1), CancellationToken.None);
- }
-
- public static Task<TOutput> ReceiveAsync<TOutput> (this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
- {
- return ReceiveAsync (source, TimeSpan.FromMilliseconds (-1), cancellationToken);
- }
-
- public static Task<TOutput> ReceiveAsync<TOutput> (this ISourceBlock<TOutput> source, TimeSpan timeout)
- {
- return ReceiveAsync (source, timeout, CancellationToken.None);
- }
-
- public static Task<TOutput> ReceiveAsync<TOutput> (
- this ISourceBlock<TOutput> source, TimeSpan timeout,
- CancellationToken cancellationToken)
- {
- if (source == null)
- throw new ArgumentNullException ("source");
- if (timeout.TotalMilliseconds < -1)
- throw new ArgumentOutOfRangeException ("timeout");
- if (timeout.TotalMilliseconds > int.MaxValue)
- throw new ArgumentOutOfRangeException ("timeout");
-
- cancellationToken.ThrowIfCancellationRequested ();
-
- int timeoutMilliseconds = (int)timeout.TotalMilliseconds;
- var block = new ReceiveBlock<TOutput> (cancellationToken, timeoutMilliseconds);
- var bridge = source.LinkTo (block);
- return block.AsyncGet (bridge);
- }
-
- public static bool TryReceive<TOutput> (this IReceivableSourceBlock<TOutput> source, out TOutput item)
- {
- item = default (TOutput);
- if (source == null)
- throw new ArgumentNullException ("source");
-
- return source.TryReceive (null, out item);
- }
-
- public static Task<bool> SendAsync<TInput> (
- this ITargetBlock<TInput> target, TInput item)
- {
- return SendAsync (target, item, CancellationToken.None);
- }
-
- public static Task<bool> SendAsync<TInput> (
- this ITargetBlock<TInput> target, TInput item,
- CancellationToken cancellationToken)
- {
- if (target == null)
- throw new ArgumentNullException ("target");
-
- cancellationToken.ThrowIfCancellationRequested ();
-
- var status = target.OfferMessage (
- new DataflowMessageHeader (1), item, null, false);
-
- if (status == DataflowMessageStatus.Accepted)
- return Task.FromResult (true);
- if (status != DataflowMessageStatus.Declined
- && status != DataflowMessageStatus.Postponed)
- return Task.FromResult (false);
-
- var block = new SendBlock<TInput> (target, item, cancellationToken);
- return block.Send ();
- }
-
- public static ITargetBlock<TInput> NullTarget<TInput>()
- {
- return new NullTargetBlock<TInput> ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// DataflowBlockOptions.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public class DataflowBlockOptions {
- static readonly DataflowBlockOptions DefaultOptions =
- new DataflowBlockOptions ();
-
- /// <summary>
- /// Cached default block options
- /// </summary>
- internal static DataflowBlockOptions Default {
- get { return DefaultOptions; }
- }
-
- public const int Unbounded = -1;
-
- int boundedCapacity;
- int maxMessagesPerTask;
- TaskScheduler taskScheduler;
- string nameFormat;
-
- public DataflowBlockOptions ()
- {
- BoundedCapacity = -1;
- CancellationToken = CancellationToken.None;
- MaxMessagesPerTask = -1;
- TaskScheduler = TaskScheduler.Default;
- NameFormat = "{0} Id={1}";
- }
-
- public int BoundedCapacity {
- get { return boundedCapacity; }
- set {
- if (value < -1)
- throw new ArgumentOutOfRangeException("value");
-
- boundedCapacity = value;
- }
- }
-
- public CancellationToken CancellationToken { get; set; }
-
- public int MaxMessagesPerTask {
- get { return maxMessagesPerTask; }
- set {
- if (value < -1)
- throw new ArgumentOutOfRangeException("value");
-
- maxMessagesPerTask = value;
- }
- }
-
- public TaskScheduler TaskScheduler {
- get { return taskScheduler; }
- set {
- if (value == null)
- throw new ArgumentNullException("value");
-
- taskScheduler = value;
- }
- }
-
- public string NameFormat {
- get { return nameFormat; }
- set {
- if (value == null)
- throw new ArgumentNullException("value");
-
- nameFormat = value;
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// DataflowLinkOptions.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public class DataflowLinkOptions {
- static readonly DataflowLinkOptions DefaultOptions =
- new DataflowLinkOptions ();
-
- int maxMessages;
-
- internal static DataflowLinkOptions Default {
- get { return DefaultOptions; }
- }
-
- public DataflowLinkOptions()
- {
- PropagateCompletion = false;
- MaxMessages = DataflowBlockOptions.Unbounded;
- Append = true;
- }
-
- public bool PropagateCompletion { get; set; }
-
- public int MaxMessages {
- get { return maxMessages; }
- set {
- if (value < -1)
- throw new ArgumentOutOfRangeException("value");
-
- maxMessages = value;
- }
- }
-
- public bool Append { get; set; }
- }
-}
\ No newline at end of file
+++ /dev/null
-// DataflowMessageHeader.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public struct DataflowMessageHeader : IEquatable<DataflowMessageHeader> {
- readonly long id;
-
- public DataflowMessageHeader (long id)
- {
- if (id == 0)
- throw new ArgumentException("The value of 0 can't be used as an id for a valid header.");
-
- this.id = id;
- }
-
- public long Id {
- get {
- return id;
- }
- }
-
- public bool IsValid {
- get {
- return id != 0;
- }
- }
-
- public override bool Equals (object obj)
- {
- return obj is DataflowMessageHeader && Equals ((DataflowMessageHeader)obj);
- }
-
- public bool Equals (DataflowMessageHeader other)
- {
- return other.id == id;
- }
-
- public override int GetHashCode ()
- {
- return id.GetHashCode ();
- }
-
- public static bool operator== (DataflowMessageHeader left, DataflowMessageHeader right)
- {
- return left.Equals (right);
- }
-
- public static bool operator!= (DataflowMessageHeader left, DataflowMessageHeader right)
- {
- return !left.Equals (right);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// DataflowMessageStatus.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-//
-
-
-using System;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow
-{
- public enum DataflowMessageStatus
- {
- Accepted,
- Declined,
- Postponed,
- NotAvailable,
- DecliningPermanently
- }
-}
-
+++ /dev/null
-// ExecutingMessageBox.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Message box for executing blocks with synchrnous actions.
- /// </summary>
- /// <typeparam name="TInput">Type of the item the block is processing.</typeparam>
- class ExecutingMessageBox<TInput> : ExecutingMessageBoxBase<TInput> {
- readonly Func<bool> processItem;
-
- public ExecutingMessageBox (
- ITargetBlock<TInput> target, BlockingCollection<TInput> messageQueue,
- CompletionHelper compHelper, Func<bool> externalCompleteTester,
- Func<bool> processItem, Action outgoingQueueComplete,
- ExecutionDataflowBlockOptions options)
- : base (
- target, messageQueue, compHelper, externalCompleteTester,
- outgoingQueueComplete, options)
- {
- this.processItem = processItem;
- }
-
- /// <summary>
- /// Processes the input queue of the block.
- /// </summary>
- protected override void ProcessQueue ()
- {
- StartProcessQueue ();
-
- try {
- int i = 0;
- while (CanRun (i)) {
- if (!processItem ())
- break;
- i++;
- }
- } catch (Exception e) {
- CompHelper.RequestFault (e, false);
- }
-
- FinishProcessQueue ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// ExecutingMessageBoxBase.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Base message box for execution blocks (synchronous and asynchrnous).
- /// </summary>
- /// <typeparam name="TInput">Type of the item the block is processing.</typeparam>
- abstract class ExecutingMessageBoxBase<TInput> : MessageBox<TInput> {
- protected ExecutionDataflowBlockOptions Options { get; private set; }
- readonly Action outgoingQueueComplete;
-
- // even number: Task is waiting to run
- // odd number: Task is not waiting to run
- // invariant: dop / 2 Tasks are running or waiting
- int degreeOfParallelism = 1;
-
- protected ExecutingMessageBoxBase (
- ITargetBlock<TInput> target, BlockingCollection<TInput> messageQueue,
- CompletionHelper compHelper, Func<bool> externalCompleteTester,
- Action outgoingQueueComplete, ExecutionDataflowBlockOptions options)
- : base (
- target, messageQueue, compHelper, externalCompleteTester,
- options)
- {
- this.Options = options;
- this.outgoingQueueComplete = outgoingQueueComplete;
- }
-
- /// <summary>
- /// Makes sure the input queue is processed the way it needs to.
- /// </summary>
- /// <param name="newItem">Was new item just added?</param>
- protected override void EnsureProcessing (bool newItem)
- {
- StartProcessing ();
- }
-
- /// <summary>
- /// Starts processing queue on a task,
- /// assuming <see cref="ExecutionDataflowBlockOptions.MaxDegreeOfParallelism"/>
- /// was't reached yet.
- /// </summary>
- void StartProcessing ()
- {
- // atomically increase degreeOfParallelism by 1 only if it's odd
- // and low enough
- int startDegreeOfParallelism;
- int currentDegreeOfParallelism = degreeOfParallelism;
- do {
- startDegreeOfParallelism = currentDegreeOfParallelism;
- if (startDegreeOfParallelism % 2 == 0
- || (Options.MaxDegreeOfParallelism != DataflowBlockOptions.Unbounded
- && startDegreeOfParallelism / 2 >= Options.MaxDegreeOfParallelism))
- return;
- currentDegreeOfParallelism =
- Interlocked.CompareExchange (ref degreeOfParallelism,
- startDegreeOfParallelism + 1, startDegreeOfParallelism);
- } while (startDegreeOfParallelism != currentDegreeOfParallelism);
-
- Task.Factory.StartNew (ProcessQueue, CancellationToken.None,
- TaskCreationOptions.PreferFairness, Options.TaskScheduler);
- }
-
- /// <summary>
- /// Processes the input queue of the block.
- /// </summary>
- /// <remarks>
- /// Should first call <see cref="StartProcessQueue"/>,
- /// then process the queue and finally call <see cref="FinishProcessQueue"/>.
- /// </remarks>
- protected abstract void ProcessQueue ();
-
- /// <summary>
- /// Notifies that another processing task was started.
- /// Should be called right after <see cref="ProcessQueue"/> is actually executed.
- /// </summary>
- protected void StartProcessQueue ()
- {
- CompHelper.CanFaultOrCancelImmediatelly = false;
-
- int incrementedDegreeOfParallelism =
- Interlocked.Increment (ref degreeOfParallelism);
- if ((Options.MaxDegreeOfParallelism == DataflowBlockOptions.Unbounded
- || incrementedDegreeOfParallelism / 2 < Options.MaxDegreeOfParallelism)
- && MessageQueue.Count > 1 && CompHelper.CanRun)
- StartProcessing ();
- }
-
- /// <summary>
- /// Notifies that a processing task was finished.
- /// Should be called after <see cref="ProcessQueue"/> actually finishes processing.
- /// </summary>
- protected void FinishProcessQueue ()
- {
- int decrementedDegreeOfParallelism =
- Interlocked.Add (ref degreeOfParallelism, -2);
-
- if (decrementedDegreeOfParallelism % 2 == 1) {
- if (decrementedDegreeOfParallelism == 1) {
- CompHelper.CanFaultOrCancelImmediatelly = true;
- base.VerifyCompleteness ();
- if (MessageQueue.IsCompleted)
- outgoingQueueComplete ();
- }
- if (MessageQueue.Count > 0 && CompHelper.CanRun)
- StartProcessing ();
- }
- }
-
- /// <summary>
- /// Notifies that outgoing queue should be completed, if possible.
- /// </summary>
- protected override void OutgoingQueueComplete ()
- {
- if (MessageQueue.IsCompleted
- && Volatile.Read (ref degreeOfParallelism) == 1)
- outgoingQueueComplete ();
- }
-
- /// <summary>
- /// Makes sure the block is completed if it should be.
- /// </summary>
- protected override void VerifyCompleteness ()
- {
- if (Volatile.Read (ref degreeOfParallelism) == 1)
- base.VerifyCompleteness ();
- }
-
- /// <summary>
- /// Indicates whether a processing task can continue executing.
- /// </summary>
- /// <param name="iteration">The number of the iteration of the task, starting from 0.</param>
- protected bool CanRun (int iteration)
- {
- return CompHelper.CanRun
- && (Options.MaxMessagesPerTask == DataflowBlockOptions.Unbounded
- || iteration < Options.MaxMessagesPerTask);
- }
- }
-}
+++ /dev/null
-// ExecutionDataflowBlockOptions.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public class ExecutionDataflowBlockOptions : DataflowBlockOptions {
- static readonly ExecutionDataflowBlockOptions DefaultOptions =
- new ExecutionDataflowBlockOptions ();
-
- int maxDegreeOfParallelism;
-
- /// <summary>
- /// Cached default block options
- /// </summary>
- internal static new ExecutionDataflowBlockOptions Default {
- get { return DefaultOptions; }
- }
-
- public ExecutionDataflowBlockOptions ()
- {
- MaxDegreeOfParallelism = 1;
- }
-
- public int MaxDegreeOfParallelism {
- get { return maxDegreeOfParallelism; }
- set {
- if (value < -1)
- throw new ArgumentOutOfRangeException("value");
-
- maxDegreeOfParallelism = value;
- }
- }
-
- public bool SingleProducerConstrained { get; set; }
- }
-}
\ No newline at end of file
+++ /dev/null
-// GroupingDataflowBlockOptions.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow
-{
- public class GroupingDataflowBlockOptions : DataflowBlockOptions {
- static readonly GroupingDataflowBlockOptions DefaultOptions =
- new GroupingDataflowBlockOptions ();
-
- long maxNumberOfGroups;
-
- /// <summary>
- /// Cached default block options
- /// </summary>
- internal static new GroupingDataflowBlockOptions Default {
- get { return DefaultOptions; }
- }
-
- public GroupingDataflowBlockOptions ()
- {
- Greedy = true;
- MaxNumberOfGroups = -1;
- }
-
- public bool Greedy { get; set; }
-
- public long MaxNumberOfGroups {
- get { return maxNumberOfGroups; }
- set {
- if (value < -1)
- throw new ArgumentOutOfRangeException("value");
-
- maxNumberOfGroups = value;
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// IDataflowBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public interface IDataflowBlock {
- Task Completion { get; }
-
- void Complete ();
- void Fault (Exception exception);
- }
-}
\ No newline at end of file
+++ /dev/null
-// IPropagatorBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public interface IPropagatorBlock<in TInput, out TOutput>
- : ITargetBlock<TInput>, ISourceBlock<TOutput> {
- }
-}
\ No newline at end of file
+++ /dev/null
-// DataflowBlockOptions.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow {
- public interface IReceivableSourceBlock<TOutput> : ISourceBlock<TOutput> {
- bool TryReceive (Predicate<TOutput> filter, out TOutput item);
- bool TryReceiveAll (out IList<TOutput> items);
- }
-}
\ No newline at end of file
+++ /dev/null
-// ISourceBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public interface ISourceBlock<out TOutput> : IDataflowBlock {
- TOutput ConsumeMessage (DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out bool messageConsumed);
- IDisposable LinkTo (ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions);
- void ReleaseReservation (DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target);
- bool ReserveMessage (DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target);
- }
-}
\ No newline at end of file
+++ /dev/null
-// ITargetBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- public interface ITargetBlock<in TInput> : IDataflowBlock {
- DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept);
- }
-}
\ No newline at end of file
+++ /dev/null
-// JoinBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow
-{
- public sealed class JoinBlock<T1, T2> : IReceivableSourceBlock<Tuple<T1, T2>>
- {
- readonly CompletionHelper compHelper;
- readonly GroupingDataflowBlockOptions dataflowBlockOptions;
- readonly OutgoingQueue<Tuple<T1, T2>> outgoing;
-
- readonly JoinTarget<T1> target1;
- readonly JoinTarget<T2> target2;
-
- SpinLock targetLock = new SpinLock(false);
- readonly AtomicBoolean nonGreedyProcessing = new AtomicBoolean ();
-
- long target1Count;
- long target2Count;
- long numberOfGroups;
-
- public JoinBlock () : this (GroupingDataflowBlockOptions.Default)
- {
- }
-
- public JoinBlock (GroupingDataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.dataflowBlockOptions = dataflowBlockOptions;
- compHelper = CompletionHelper.GetNew (dataflowBlockOptions);
- target1 = new JoinTarget<T1> (this, SignalArrivalTarget, compHelper,
- () => outgoing.IsCompleted, dataflowBlockOptions,
- dataflowBlockOptions.Greedy, TryAdd1);
- target2 = new JoinTarget<T2> (this, SignalArrivalTarget, compHelper,
- () => outgoing.IsCompleted, dataflowBlockOptions,
- dataflowBlockOptions.Greedy, TryAdd2);
- outgoing = new OutgoingQueue<Tuple<T1, T2>> (this, compHelper,
- () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted,
- _ =>
- {
- target1.DecreaseCount ();
- target2.DecreaseCount ();
- }, dataflowBlockOptions);
- }
-
- public IDisposable LinkTo (ITargetBlock<Tuple<T1, T2>> target, DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- public bool TryReceive (Predicate<Tuple<T1, T2>> filter, out Tuple<T1, T2> item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<Tuple<T1, T2>> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- Tuple<T1, T2> ISourceBlock<Tuple<T1, T2>>.ConsumeMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2>> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- void ISourceBlock<Tuple<T1, T2>>.ReleaseReservation (
- DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2>> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<Tuple<T1, T2>>.ReserveMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2>> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public void Complete ()
- {
- target1.Complete ();
- target2.Complete ();
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted by the first target,
- /// and increments a counter if it can.
- /// </summary>
- bool TryAdd1 ()
- {
- return dataflowBlockOptions.MaxNumberOfGroups == -1
- || Interlocked.Increment (ref target1Count)
- <= dataflowBlockOptions.MaxNumberOfGroups;
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted by the second target,
- /// and increments a counter if it can.
- /// </summary>
- bool TryAdd2 ()
- {
- return dataflowBlockOptions.MaxNumberOfGroups == -1
- || Interlocked.Increment (ref target2Count)
- <= dataflowBlockOptions.MaxNumberOfGroups;
- }
-
- /// <summary>
- /// Decides whether to create a new tuple or not.
- /// </summary>
- void SignalArrivalTarget ()
- {
- if (dataflowBlockOptions.Greedy) {
- bool taken = false;
- T1 value1;
- T2 value2;
-
- try {
- targetLock.Enter (ref taken);
-
- if (target1.Buffer.Count == 0 || target2.Buffer.Count == 0)
- return;
-
- value1 = target1.Buffer.Take ();
- value2 = target2.Buffer.Take ();
- } finally {
- if (taken)
- targetLock.Exit ();
- }
-
- TriggerMessage (value1, value2);
- } else {
- if (ShouldProcessNonGreedy ())
- EnsureNonGreedyProcessing ();
- }
- }
-
- /// <summary>
- /// Returns whether non-greedy creation of a tuple should be started.
- /// </summary>
- bool ShouldProcessNonGreedy ()
- {
- return target1.PostponedMessagesCount >= 1
- && target2.PostponedMessagesCount >= 1
- && (dataflowBlockOptions.BoundedCapacity == -1
- || outgoing.Count < dataflowBlockOptions.BoundedCapacity);
- }
-
- /// <summary>
- /// Starts non-greedy creation of tuples, if one doesn't already run.
- /// </summary>
- void EnsureNonGreedyProcessing ()
- {
- if (nonGreedyProcessing.TrySet ())
- Task.Factory.StartNew (NonGreedyProcess,
- dataflowBlockOptions.CancellationToken,
- TaskCreationOptions.PreferFairness,
- dataflowBlockOptions.TaskScheduler);
- }
-
- /// <summary>
- /// Creates tuples in non-greedy mode,
- /// making sure the whole tuple is available by using reservations.
- /// </summary>
- void NonGreedyProcess()
- {
- while (ShouldProcessNonGreedy ()) {
- var reservation1 = target1.ReserveMessage ();
-
- if (reservation1 == null)
- break;
-
- var reservation2 = target2.ReserveMessage ();
- if (reservation2 == null) {
- target1.RelaseReservation (reservation1);
- break;
- }
-
- var value1 = target1.ConsumeReserved (reservation1);
- var value2 = target2.ConsumeReserved (reservation2);
-
- TriggerMessage (value1, value2);
- }
-
- nonGreedyProcessing.Value = false;
-
- if (ShouldProcessNonGreedy ())
- EnsureNonGreedyProcessing ();
- }
-
-
- /// <summary>
- /// Creates a tuple from the given values and adds the result to the output queue.
- /// </summary>
- void TriggerMessage (T1 val1, T2 val2)
- {
- outgoing.AddData (Tuple.Create (val1, val2));
-
- if (dataflowBlockOptions.MaxNumberOfGroups != -1
- && Interlocked.Increment (ref numberOfGroups)
- >= dataflowBlockOptions.MaxNumberOfGroups)
- Complete ();
- }
-
- public ITargetBlock<T1> Target1 {
- get { return target1; }
- }
-
- public ITargetBlock<T2> Target2 {
- get { return target2; }
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// JoinBlock`3.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow
-{
- public sealed class JoinBlock<T1, T2, T3> : IReceivableSourceBlock<Tuple<T1, T2, T3>>
- {
- readonly CompletionHelper compHelper;
- readonly GroupingDataflowBlockOptions dataflowBlockOptions;
- readonly OutgoingQueue<Tuple<T1, T2, T3>> outgoing;
-
- readonly JoinTarget<T1> target1;
- readonly JoinTarget<T2> target2;
- readonly JoinTarget<T3> target3;
-
- SpinLock targetLock = new SpinLock (false);
- readonly AtomicBoolean nonGreedyProcessing = new AtomicBoolean ();
-
- long target1Count;
- long target2Count;
- long target3Count;
- long numberOfGroups;
-
- public JoinBlock () : this (GroupingDataflowBlockOptions.Default)
- {
- }
-
- public JoinBlock (GroupingDataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions);
-
- target1 = new JoinTarget<T1> (this, SignalArrivalTarget, compHelper,
- () => outgoing.IsCompleted, dataflowBlockOptions,
- dataflowBlockOptions.Greedy, TryAdd1);
- target2 = new JoinTarget<T2> (this, SignalArrivalTarget, compHelper,
- () => outgoing.IsCompleted, dataflowBlockOptions,
- dataflowBlockOptions.Greedy, TryAdd2);
- target3 = new JoinTarget<T3> (this, SignalArrivalTarget, compHelper,
- () => outgoing.IsCompleted, dataflowBlockOptions,
- dataflowBlockOptions.Greedy, TryAdd3);
- outgoing = new OutgoingQueue<Tuple<T1, T2, T3>> (
- this, compHelper,
- () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted
- || target3.Buffer.IsCompleted,
- _ =>
- {
- target1.DecreaseCount ();
- target2.DecreaseCount ();
- target3.DecreaseCount ();
- }, dataflowBlockOptions);
- }
-
- public IDisposable LinkTo (ITargetBlock<Tuple<T1, T2, T3>> target, DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- public bool TryReceive (Predicate<Tuple<T1, T2, T3>> filter, out Tuple<T1, T2, T3> item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<Tuple<T1, T2, T3>> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- Tuple<T1, T2, T3> ISourceBlock<Tuple<T1, T2, T3>>.ConsumeMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2, T3>> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- void ISourceBlock<Tuple<T1, T2, T3>>.ReleaseReservation (
- DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2, T3>> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<Tuple<T1, T2, T3>>.ReserveMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<Tuple<T1, T2, T3>> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public void Complete ()
- {
- target1.Complete ();
- target2.Complete ();
- target3.Complete ();
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted by the first target,
- /// and increments a counter if it can.
- /// </summary>
- bool TryAdd1 ()
- {
- return dataflowBlockOptions.MaxNumberOfGroups == -1
- || Interlocked.Increment (ref target1Count)
- <= dataflowBlockOptions.MaxNumberOfGroups;
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted by the second target,
- /// and increments a counter if it can.
- /// </summary>
- bool TryAdd2 ()
- {
- return dataflowBlockOptions.MaxNumberOfGroups == -1
- || Interlocked.Increment (ref target2Count)
- <= dataflowBlockOptions.MaxNumberOfGroups;
- }
-
- /// <summary>
- /// Returns whether a new item can be accepted by the third target,
- /// and increments a counter if it can.
- /// </summary>
- bool TryAdd3 ()
- {
- return dataflowBlockOptions.MaxNumberOfGroups == -1
- || Interlocked.Increment (ref target3Count)
- <= dataflowBlockOptions.MaxNumberOfGroups;
- }
-
- /// <summary>
- /// Decides whether to create a new tuple or not.
- /// </summary>
- void SignalArrivalTarget ()
- {
- if (dataflowBlockOptions.Greedy) {
- bool taken = false;
- T1 value1;
- T2 value2;
- T3 value3;
-
- try {
- targetLock.Enter (ref taken);
-
- if (target1.Buffer.Count == 0 || target2.Buffer.Count == 0
- || target3.Buffer.Count == 0)
- return;
-
- value1 = target1.Buffer.Take ();
- value2 = target2.Buffer.Take ();
- value3 = target3.Buffer.Take ();
- } finally {
- if (taken)
- targetLock.Exit ();
- }
-
- TriggerMessage (value1, value2, value3);
- } else {
- if (ShouldProcesNonGreedy ())
- EnsureNonGreedyProcessing ();
- }
- }
-
- /// <summary>
- /// Returns whether non-greedy creation of a tuple should be started.
- /// </summary>
- bool ShouldProcesNonGreedy ()
- {
- return target1.PostponedMessagesCount >= 1
- && target2.PostponedMessagesCount >= 1
- && target3.PostponedMessagesCount >= 1
- && (dataflowBlockOptions.BoundedCapacity == -1
- || outgoing.Count < dataflowBlockOptions.BoundedCapacity);
- }
-
- /// <summary>
- /// Starts non-greedy creation of tuples, if one doesn't already run.
- /// </summary>
- void EnsureNonGreedyProcessing ()
- {
- if (nonGreedyProcessing.TrySet())
- Task.Factory.StartNew (NonGreedyProcess,
- dataflowBlockOptions.CancellationToken,
- TaskCreationOptions.PreferFairness,
- dataflowBlockOptions.TaskScheduler);
- }
-
- /// <summary>
- /// Creates tuples in non-greedy mode,
- /// making sure the whole tuple is available by using reservations.
- /// </summary>
- void NonGreedyProcess ()
- {
- while (ShouldProcesNonGreedy ()) {
- var reservation1 = target1.ReserveMessage ();
-
- if (reservation1 == null)
- break;
-
- var reservation2 = target2.ReserveMessage ();
- if (reservation2 == null) {
- target1.RelaseReservation (reservation1);
- break;
- }
-
- var reservation3 = target3.ReserveMessage ();
- if (reservation3 == null) {
- target1.RelaseReservation (reservation1);
- target2.RelaseReservation (reservation2);
- break;
- }
-
- var value1 = target1.ConsumeReserved (reservation1);
- var value2 = target2.ConsumeReserved (reservation2);
- var value3 = target3.ConsumeReserved (reservation3);
-
- TriggerMessage (value1, value2, value3);
- }
-
- nonGreedyProcessing.Value = false;
-
- if (ShouldProcesNonGreedy ())
- EnsureNonGreedyProcessing ();
- }
-
- /// <summary>
- /// Creates a tuple from the given values and adds the result to the output queue.
- /// </summary>
- void TriggerMessage (T1 val1, T2 val2, T3 val3)
- {
- outgoing.AddData (Tuple.Create (val1, val2, val3));
-
- if (dataflowBlockOptions.MaxNumberOfGroups != -1
- && Interlocked.Increment (ref numberOfGroups)
- >= dataflowBlockOptions.MaxNumberOfGroups)
- Complete ();
- }
-
- public ITargetBlock<T1> Target1 {
- get { return target1; }
- }
-
- public ITargetBlock<T2> Target2 {
- get { return target2; }
- }
-
- public ITargetBlock<T3> Target3 {
- get { return target3; }
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// JoinBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Target block use by join blocks in their TargetN properties.
- /// Also serves as its own <see cref="MessageBox{TInput}"/>.
- /// </summary>
- class JoinTarget<TTarget> : MessageBox<TTarget>, ITargetBlock<TTarget> {
- readonly IDataflowBlock joinBlock;
- readonly Action signal;
-
- public JoinTarget (
- IDataflowBlock joinBlock, Action signal, CompletionHelper helper,
- Func<bool> externalCompleteTester, DataflowBlockOptions options,
- bool greedy, Func<bool> canAccept)
- : base (null, new BlockingCollection<TTarget> (), helper, externalCompleteTester,
- options, greedy, canAccept)
- {
- this.joinBlock = joinBlock;
- this.signal = signal;
- Target = this;
- }
-
- /// <summary>
- /// Makes sure the input queue is processed the way it needs to,
- /// by signaling the parent join block.
- /// </summary>
- protected override void EnsureProcessing (bool newItem)
- {
- signal ();
- }
-
- /// <summary>
- /// The input queue of this block.
- /// </summary>
- public BlockingCollection<TTarget> Buffer {
- get { return MessageQueue; }
- }
-
- DataflowMessageStatus ITargetBlock<TTarget>.OfferMessage (
- DataflowMessageHeader messageHeader, TTarget messageValue,
- ISourceBlock<TTarget> source, bool consumeToAccept)
- {
- return OfferMessage (messageHeader, messageValue, source, consumeToAccept);
- }
-
- void IDataflowBlock.Complete ()
- {
- Complete ();
- }
-
- Task IDataflowBlock.Completion {
- get { throw new NotSupportedException (); }
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- joinBlock.Fault (exception);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// MessageBox.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-using System.Linq;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// In MessageBox we store message that have been offered to us so that they can be
- /// later processed
- /// </summary>
- internal abstract class MessageBox<TInput> {
- protected ITargetBlock<TInput> Target { get; set; }
- protected CompletionHelper CompHelper { get; private set; }
- readonly Func<bool> externalCompleteTester;
- readonly DataflowBlockOptions options;
- readonly bool greedy;
- readonly Func<bool> canAccept;
-
- readonly ConcurrentDictionary<ISourceBlock<TInput>, DataflowMessageHeader>
- postponedMessages =
- new ConcurrentDictionary<ISourceBlock<TInput>, DataflowMessageHeader> ();
- int itemCount;
- readonly AtomicBoolean postponedProcessing = new AtomicBoolean ();
-
- // these two fields are used only in one special case
- SpinLock consumingLock;
- // this is necessary, because canAccept is not pure
- bool canAcceptFromBefore;
-
- protected BlockingCollection<TInput> MessageQueue { get; private set; }
-
- protected MessageBox (
- ITargetBlock<TInput> target, BlockingCollection<TInput> messageQueue,
- CompletionHelper compHelper, Func<bool> externalCompleteTester,
- DataflowBlockOptions options, bool greedy = true, Func<bool> canAccept = null)
- {
- this.Target = target;
- this.CompHelper = compHelper;
- this.MessageQueue = messageQueue;
- this.externalCompleteTester = externalCompleteTester;
- this.options = options;
- this.greedy = greedy;
- this.canAccept = canAccept;
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (consumeToAccept && source == null)
- throw new ArgumentException (
- "consumeToAccept may only be true if provided with a non-null source.",
- "consumeToAccept");
-
- if (MessageQueue.IsAddingCompleted || !CompHelper.CanRun)
- return DataflowMessageStatus.DecliningPermanently;
-
- var full = options.BoundedCapacity != -1
- && Volatile.Read (ref itemCount) >= options.BoundedCapacity;
- if (!greedy || full) {
- if (source == null)
- return DataflowMessageStatus.Declined;
-
- postponedMessages [source] = messageHeader;
-
- // necessary to avoid race condition
- DecreaseCount (0);
-
- if (!greedy && !full)
- EnsureProcessing (true);
-
- return DataflowMessageStatus.Postponed;
- }
-
- // in this case, we need to use locking to make sure
- // we don't consume when we can't accept
- if (consumeToAccept && canAccept != null) {
- bool lockTaken = false;
- try {
- consumingLock.Enter (ref lockTaken);
- if (!canAcceptFromBefore && !canAccept ())
- return DataflowMessageStatus.DecliningPermanently;
-
- bool consummed;
- messageValue = source.ConsumeMessage (messageHeader, Target, out consummed);
- if (!consummed) {
- canAcceptFromBefore = true;
- return DataflowMessageStatus.NotAvailable;
- }
-
- canAcceptFromBefore = false;
- } finally {
- if (lockTaken)
- consumingLock.Exit ();
- }
- } else {
- if (consumeToAccept) {
- bool consummed;
- messageValue = source.ConsumeMessage (messageHeader, Target, out consummed);
- if (!consummed)
- return DataflowMessageStatus.NotAvailable;
- }
-
- if (canAccept != null && !canAccept ())
- return DataflowMessageStatus.DecliningPermanently;
- }
-
- try {
- MessageQueue.Add (messageValue);
- } catch (InvalidOperationException) {
- // This is triggered either if the underlying collection didn't accept the item
- // or if the messageQueue has been marked complete, either way it corresponds to a false
- return DataflowMessageStatus.DecliningPermanently;
- }
-
- IncreaseCount ();
-
- EnsureProcessing (true);
-
- VerifyCompleteness ();
-
- return DataflowMessageStatus.Accepted;
- }
-
- /// <summary>
- /// Increses the count of items in the block by 1.
- /// </summary>
- public void IncreaseCount ()
- {
- Interlocked.Increment (ref itemCount);
- }
-
- /// <summary>
- /// Decreses the number of items in the block by the given count.
- /// </summary>
- /// <remarks>
- /// The <paramref name="count"/> parameter is used when one object
- /// can represent many items, like a batch in <see cref="BatchBlock{T}"/>.
- /// </remarks>
- public void DecreaseCount (int count = 1)
- {
- int decreased = Interlocked.Add (ref itemCount, -count);
-
- // if BoundedCapacity is -1, there is no need to do this
- if (decreased < options.BoundedCapacity && !postponedMessages.IsEmpty) {
- if (greedy)
- EnsurePostponedProcessing ();
- else
- EnsureProcessing (false);
- }
- }
-
- /// <summary>
- /// The number of messages that were postponed
- /// and can be attempted to be consumed.
- /// </summary>
- public int PostponedMessagesCount {
- get { return postponedMessages.Count; }
- }
-
- /// <summary>
- /// Reserves a message from those that were postponed.
- /// Does not guarantee any order of the messages being reserved.
- /// </summary>
- /// <returns>
- /// An object representing the reservation on success,
- /// <c>null</c> on failure.
- /// </returns>
- public Tuple<ISourceBlock<TInput>, DataflowMessageHeader> ReserveMessage()
- {
- while (!postponedMessages.IsEmpty) {
- // KeyValuePair is a struct, so default value is not null
- var block = postponedMessages.FirstOrDefault () .Key;
-
- // collection is empty
- if (block == null)
- break;
-
- DataflowMessageHeader header;
- bool removed = postponedMessages.TryRemove (block, out header);
-
- // another thread was faster, try again
- if (!removed)
- continue;
-
- bool reserved = block.ReserveMessage (header, Target);
- if (reserved)
- return Tuple.Create (block, header);
- }
-
- return null;
- }
-
- /// <summary>
- /// Releases the given reservation.
- /// </summary>
- public void RelaseReservation(Tuple<ISourceBlock<TInput>, DataflowMessageHeader> reservation)
- {
- reservation.Item1.ReleaseReservation (reservation.Item2, Target);
- }
-
- /// <summary>
- /// Consumes previously reserved item.
- /// </summary>
- public TInput ConsumeReserved(Tuple<ISourceBlock<TInput>, DataflowMessageHeader> reservation)
- {
- bool consumed;
- return reservation.Item1.ConsumeMessage (
- reservation.Item2, Target, out consumed);
- }
-
- /// <summary>
- /// Makes sure retrieving items that were postponed,
- /// because they would exceed <see cref="DataflowBlockOptions.BoundedCapacity"/>,
- /// is currently running.
- /// </summary>
- void EnsurePostponedProcessing ()
- {
- if (postponedProcessing.TrySet())
- Task.Factory.StartNew (RetrievePostponed, options.CancellationToken,
- TaskCreationOptions.PreferFairness, options.TaskScheduler);
- }
-
- /// <summary>
- /// Retrieves items that were postponed,
- /// because they would exceed <see cref="DataflowBlockOptions.BoundedCapacity"/>.
- /// </summary>
- void RetrievePostponed ()
- {
- // BoundedCapacity can't be -1 here, because in that case there would be no postponing
- while (Volatile.Read (ref itemCount) < options.BoundedCapacity
- && !postponedMessages.IsEmpty && !MessageQueue.IsAddingCompleted) {
- var block = postponedMessages.First ().Key;
- DataflowMessageHeader header;
- postponedMessages.TryRemove (block, out header);
-
- bool consumed;
- var item = block.ConsumeMessage (header, Target, out consumed);
- if (consumed) {
- try {
- MessageQueue.Add (item);
- IncreaseCount ();
- EnsureProcessing (false);
- } catch (InvalidOperationException) {
- break;
- }
- }
- }
-
- // release all postponed messages
- if (MessageQueue.IsAddingCompleted) {
- while (!postponedMessages.IsEmpty) {
- var block = postponedMessages.First ().Key;
- DataflowMessageHeader header;
- postponedMessages.TryRemove (block, out header);
-
- if (block.ReserveMessage (header, Target))
- block.ReleaseReservation (header, Target);
- }
- }
-
- postponedProcessing.Value = false;
-
- // because of race
- if ((Volatile.Read (ref itemCount) < options.BoundedCapacity
- || MessageQueue.IsAddingCompleted)
- && !postponedMessages.IsEmpty)
- EnsurePostponedProcessing ();
- }
-
- /// <summary>
- /// Makes sure the input queue is processed the way it needs to.
- /// </summary>
- /// <param name="newItem">Was new item just added?</param>
- protected abstract void EnsureProcessing (bool newItem);
-
- /// <summary>
- /// Completes the box, no new messages will be accepted.
- /// Also starts the process of completing the output queue.
- /// </summary>
- public void Complete ()
- {
- // Make message queue complete
- MessageQueue.CompleteAdding ();
- OutgoingQueueComplete ();
- VerifyCompleteness ();
-
- if (!postponedMessages.IsEmpty)
- EnsurePostponedProcessing ();
- }
-
- /// <summary>
- /// Notifies that outgoing queue should be completed, if possible.
- /// </summary>
- protected virtual void OutgoingQueueComplete ()
- {
- }
-
- /// <summary>
- /// Makes sure the block is completed if it should be.
- /// </summary>
- protected virtual void VerifyCompleteness ()
- {
- if (MessageQueue.IsCompleted && externalCompleteTester ())
- CompHelper.Complete ();
- }
- }
-}
+++ /dev/null
-// NameHelper.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Helper class for figuring out the name of a block.
- /// </summary>
- static class NameHelper {
- /// <summary>
- /// Returns the name of the block, based on <see cref="DataflowBlockOptions.NameFormat"/>.
- /// </summary>
- /// <remarks>
- /// If the NameFormat is invalid, returns the exception message.
- /// </remarks>
- public static string GetName(IDataflowBlock block, DataflowBlockOptions options)
- {
- try {
- return string.Format (
- options.NameFormat, block.GetType ().Name, block.Completion.Id);
- } catch (FormatException e) {
- return e.Message;
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// NullTargetBlock.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Target block returned by <see cref="DataflowBlock.NullTarget{TInput}"/>.
- /// </summary>
- class NullTargetBlock<TInput> : ITargetBlock<TInput> {
- public NullTargetBlock ()
- {
- Completion = new TaskCompletionSource<bool> ().Task;
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (consumeToAccept && source == null)
- throw new ArgumentException (
- "consumeToAccept may only be true if provided with a non-null source.",
- "consumeToAccept");
-
- if (consumeToAccept) {
- if (!source.ReserveMessage (messageHeader, this))
- return DataflowMessageStatus.NotAvailable;
- bool consummed;
- source.ConsumeMessage (messageHeader, this, out consummed);
- if (!consummed)
- return DataflowMessageStatus.NotAvailable;
- }
-
- return DataflowMessageStatus.Accepted;
- }
-
- public Task Completion { get; private set; }
-
- public void Complete ()
- {
- }
-
- public void Fault (Exception exception)
- {
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// ObservableDataflowBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Rx Observable that represents a source block.
- /// </summary>
- class ObservableDataflowBlock<TSource> : IObservable<TSource> {
- class ObserverWrapper : ITargetBlock<TSource> {
- readonly IObserver<TSource> observer;
-
- public ObserverWrapper (IObserver<TSource> observer)
- {
- this.observer = observer;
- }
-
- public void Complete ()
- {
- observer.OnCompleted ();
- }
-
- public void Fault (Exception exception)
- {
- observer.OnError (exception);
- }
-
- public Task Completion {
- get { return null; }
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TSource messageValue,
- ISourceBlock<TSource> source, bool consumeToAccept)
- {
- if (consumeToAccept) {
- if (!source.ReserveMessage (messageHeader, this))
- return DataflowMessageStatus.NotAvailable;
- bool consumed;
- messageValue = source.ConsumeMessage (messageHeader, this, out consumed);
- if (!consumed)
- return DataflowMessageStatus.NotAvailable;
- }
-
- observer.OnNext (messageValue);
-
- return DataflowMessageStatus.Accepted;
- }
- }
-
- readonly ISourceBlock<TSource> source;
-
- public ObservableDataflowBlock (ISourceBlock<TSource> source)
- {
- this.source = source;
- }
-
- public IDisposable Subscribe (IObserver<TSource> observer)
- {
- var wrapper = new ObserverWrapper (observer);
- return source.LinkTo (wrapper);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// ObserverDataflowBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Rx Observer that represents a target block.
- /// </summary>
- class ObserverDataflowBlock<TInput> : IObserver<TInput> {
- readonly ITargetBlock<TInput> target;
-
- public ObserverDataflowBlock (ITargetBlock<TInput> target)
- {
- this.target = target;
- }
-
- public void OnCompleted ()
- {
- target.Complete ();
- }
-
- public void OnError (Exception ex)
- {
- target.Fault (ex);
- }
-
- public void OnNext (TInput value)
- {
- target.Post (value);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// OutgoingQueue.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Version of <see cref="OutgoingQueueBase{T}"/> for
- /// non-broadcast blocks.
- /// </summary>
- class OutgoingQueue<T> : OutgoingQueueBase<T> {
- readonly Func<T, int> countSelector;
- SpinLock firstItemLock = new SpinLock();
- volatile ITargetBlock<T> reservedForTargetBlock;
- readonly TargetCollection<T> targets;
-
- protected override TargetCollectionBase<T> Targets {
- get { return targets; }
- }
-
- public OutgoingQueue (
- ISourceBlock<T> block, CompletionHelper compHelper,
- Func<bool> externalCompleteTester, Action<int> decreaseItemsCount,
- DataflowBlockOptions options, Func<T, int> countSelector = null)
- : base (compHelper, externalCompleteTester,
- decreaseItemsCount, options)
- {
- targets = new TargetCollection<T> (block);
- this.countSelector = countSelector;
- }
-
- /// <summary>
- /// Calculates the count of items in the given object.
- /// </summary>
- protected override int GetModifiedCount(T data)
- {
- if (countSelector == null)
- return 1;
-
- return countSelector (data);
- }
-
- /// <summary>
- /// Sends messages to targets.
- /// </summary>
- protected override void Process ()
- {
- bool processed;
- do {
- ForceProcessing = false;
-
- bool lockTaken = false;
- try {
- firstItemLock.Enter (ref lockTaken);
-
- T item;
- if (!Store.TryPeek (out item))
- break;
-
- if (!targets.HasCurrentItem)
- targets.SetCurrentItem (item);
-
- if (reservedForTargetBlock != null)
- break;
-
- processed = targets.OfferItemToTargets ();
- if (processed) {
- Outgoing.TryTake (out item);
- DecreaseCounts (item);
- FirstItemChanged ();
- }
- } finally {
- if (lockTaken)
- firstItemLock.Exit ();
- }
- } while (processed);
-
- IsProcessing.Value = false;
-
- // to guard against race condition
- if (ForceProcessing && reservedForTargetBlock == null)
- EnsureProcessing ();
-
- VerifyCompleteness ();
- }
-
- public T ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> targetBlock, out bool messageConsumed)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (targetBlock == null)
- throw new ArgumentNullException("target");
-
- T result = default(T);
- messageConsumed = false;
-
- bool lockTaken = false;
- try {
- firstItemLock.Enter (ref lockTaken);
-
- if (targets.VerifyHeader (messageHeader, targetBlock)
- && (reservedForTargetBlock == null
- || reservedForTargetBlock == targetBlock)) {
- // cannot consume from faulted block, unless reserved
- if (reservedForTargetBlock == null && IsFaultedOrCancelled)
- return result;
-
- Outgoing.TryTake (out result);
- messageConsumed = true;
- DecreaseCounts (result);
- reservedForTargetBlock = null;
- FirstItemChanged ();
- }
- } finally {
- if (lockTaken)
- firstItemLock.Exit ();
- }
-
- targets.UnpostponeTarget (targetBlock, messageConsumed);
- EnsureProcessing ();
- VerifyCompleteness ();
-
- return result;
- }
-
- public bool ReserveMessage (DataflowMessageHeader messageHeader, ITargetBlock<T> target)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (target == null)
- throw new ArgumentNullException("target");
-
- bool lockTaken = false;
- try {
- firstItemLock.Enter (ref lockTaken);
-
- if (targets.VerifyHeader(messageHeader, target)) {
- reservedForTargetBlock = target;
- return true;
- }
-
- targets.UnpostponeTarget (target, false);
- EnsureProcessing ();
-
- return false;
- } finally {
- if (lockTaken)
- firstItemLock.Exit ();
- }
- }
-
- public void ReleaseReservation (DataflowMessageHeader messageHeader, ITargetBlock<T> target)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (target == null)
- throw new ArgumentNullException("target");
-
- bool lockTaken = false;
- try
- {
- firstItemLock.Enter(ref lockTaken);
-
- if (!targets.VerifyHeader(messageHeader, target)
- || reservedForTargetBlock != target)
- throw new InvalidOperationException(
- "The target did not have the message reserved.");
-
- reservedForTargetBlock = null;
- } finally {
- if (lockTaken)
- firstItemLock.Exit ();
- }
-
- targets.UnpostponeTarget (target, false);
- EnsureProcessing ();
- }
-
- /// <summary>
- /// Notifies that the first item in the queue changed.
- /// </summary>
- void FirstItemChanged ()
- {
- T firstItem;
- if (Store.TryPeek (out firstItem))
- targets.SetCurrentItem (firstItem);
- else
- targets.ResetCurrentItem ();
- }
-
- public bool TryReceive (Predicate<T> filter, out T item)
- {
- bool success = false;
- item = default (T);
-
- bool lockTaken = false;
- try {
- firstItemLock.Enter (ref lockTaken);
-
- if (reservedForTargetBlock != null)
- return false;
-
- T result;
- if (Store.TryPeek (out result) && (filter == null || filter (result))) {
- Outgoing.TryTake (out item);
- success = true;
- DecreaseCounts (item);
- FirstItemChanged ();
- }
- } finally {
- if (lockTaken)
- firstItemLock.Exit ();
- }
-
- EnsureProcessing ();
- VerifyCompleteness ();
-
- return success;
- }
-
- public bool TryReceiveAll (out IList<T> items)
- {
- items = null;
-
- if (Store.IsEmpty)
- return false;
-
- bool lockTaken = false;
- try {
- firstItemLock.Enter (ref lockTaken);
-
- if (reservedForTargetBlock != null)
- return false;
-
- var list = new List<T> (Outgoing.Count);
-
- T item;
- while (Outgoing.TryTake (out item)) {
- DecreaseCounts (item);
- list.Add (item);
- }
-
- items = list;
-
- FirstItemChanged ();
- } finally {
- if (lockTaken)
- firstItemLock.Exit ();
- }
-
- EnsureProcessing ();
- VerifyCompleteness ();
-
- return items.Count > 0;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// OutgoingQueueBase.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Handles outgoing messages that get queued when there is no
- /// block on the other end to proces it. It also allows receive operations.
- /// </summary>
- abstract class OutgoingQueueBase<T> {
- protected ConcurrentQueue<T> Store { get; private set; }
- protected BlockingCollection<T> Outgoing { get; private set; }
- int outgoingCount;
- readonly CompletionHelper compHelper;
- readonly Func<bool> externalCompleteTester;
- readonly DataflowBlockOptions options;
- protected AtomicBoolean IsProcessing { get; private set; }
- protected abstract TargetCollectionBase<T> Targets { get; }
- int totalModifiedCount;
- readonly Action<int> decreaseItemsCount;
- volatile bool forceProcessing;
-
- protected OutgoingQueueBase (
- CompletionHelper compHelper, Func<bool> externalCompleteTester,
- Action<int> decreaseItemsCount, DataflowBlockOptions options)
- {
- IsProcessing = new AtomicBoolean ();
- Store = new ConcurrentQueue<T> ();
- Outgoing = new BlockingCollection<T> (Store);
- this.compHelper = compHelper;
- this.externalCompleteTester = externalCompleteTester;
- this.options = options;
- this.decreaseItemsCount = decreaseItemsCount;
- }
-
- /// <summary>
- /// Is the queue completed?
- /// Queue is completed after <see cref="Complete"/> is called
- /// and all items are retrieved from it.
- /// </summary>
- public bool IsCompleted {
- get { return Outgoing.IsCompleted; }
- }
-
- /// <summary>
- /// Current number of items in the queue.
- /// Item are counted the way <see cref="DataflowBlockOptions.BoundedCapacity"/>
- /// counts them, e.g. each item in a batch counts, even if batch is a single object.
- /// </summary>
- public int Count {
- get { return totalModifiedCount; }
- }
-
- /// <summary>
- /// Calculates the count of items in the given object.
- /// </summary>
- protected virtual int GetModifiedCount (T data)
- {
- return 1;
- }
-
- /// <summary>
- /// Adds an object to the queue.
- /// </summary>
- public void AddData (T data)
- {
- try {
- Outgoing.Add (data);
- Interlocked.Add (ref totalModifiedCount, GetModifiedCount (data));
- if (Interlocked.Increment (ref outgoingCount) == 1)
- EnsureProcessing ();
- } catch (InvalidOperationException) {
- VerifyCompleteness ();
- }
- }
-
- /// <summary>
- /// Makes sure sending messages to targets is running.
- /// </summary>
- protected void EnsureProcessing ()
- {
- ForceProcessing = true;
- if (IsProcessing.TrySet())
- Task.Factory.StartNew (Process, CancellationToken.None,
- TaskCreationOptions.PreferFairness, options.TaskScheduler);
- }
-
- /// <summary>
- /// Indicates whether sending messages should be forced to start.
- /// </summary>
- protected bool ForceProcessing {
- get { return forceProcessing; }
- set { forceProcessing = value; }
- }
-
- /// <summary>
- /// Sends messages to targets.
- /// </summary>
- protected abstract void Process ();
-
- /// <summary>
- /// Adds a target block to send messages to.
- /// </summary>
- /// <returns>
- /// An object that can be used to destroy the link to the added target.
- /// </returns>
- public IDisposable AddTarget (ITargetBlock<T> targetBlock, DataflowLinkOptions linkOptions)
- {
- if (targetBlock == null)
- throw new ArgumentNullException ("targetBlock");
- if (linkOptions == null)
- throw new ArgumentNullException ("linkOptions");
-
- var result = Targets.AddTarget (targetBlock, linkOptions);
- EnsureProcessing ();
- return result;
- }
-
- /// <summary>
- /// Makes sure the block is completed if it should be.
- /// </summary>
- protected void VerifyCompleteness ()
- {
- if (Outgoing.IsCompleted && externalCompleteTester ())
- compHelper.Complete ();
- }
-
- /// <summary>
- /// Is the block faulted or cancelled?
- /// </summary>
- protected bool IsFaultedOrCancelled {
- get { return compHelper.Completion.IsFaulted || compHelper.Completion.IsCanceled; }
- }
-
- /// <summary>
- /// Used to notify that object was removed from the queue
- /// and to update counts.
- /// </summary>
- protected void DecreaseCounts (T data)
- {
- var modifiedCount = GetModifiedCount (data);
- Interlocked.Add (ref totalModifiedCount, -modifiedCount);
- Interlocked.Decrement (ref outgoingCount);
- decreaseItemsCount (modifiedCount);
- }
-
- /// <summary>
- /// Marks the queue for completion.
- /// </summary>
- public void Complete ()
- {
- Outgoing.CompleteAdding ();
- VerifyCompleteness ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// OutputAvailableBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// This internal block is used by the <see cref="DataflowBlock.OutputAvailableAsync"/> methods
- /// to check for available items in an asynchronous way.
- /// </summary>
- class OutputAvailableBlock<TOutput> : ITargetBlock<TOutput> {
- readonly TaskCompletionSource<bool> completion =
- new TaskCompletionSource<bool> ();
- IDisposable linkBridge;
- CancellationTokenRegistration cancellationRegistration;
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TOutput messageValue,
- ISourceBlock<TOutput> source, bool consumeToAccept)
- {
- if (!messageHeader.IsValid)
- return DataflowMessageStatus.Declined;
-
- if (completion.Task.Status != TaskStatus.WaitingForActivation)
- return DataflowMessageStatus.DecliningPermanently;
-
- completion.TrySetResult (true);
- CompletionSet ();
-
- return DataflowMessageStatus.DecliningPermanently;
- }
-
- /// <summary>
- /// Returns a Task that can be used to wait until output from a block is available.
- /// </summary>
- /// <param name="bridge">The disposable object returned by <see cref="ISourceBlock{TOutput}.LinkTo"/>.</param>
- /// <param name="token">Cancellation token for this operation.</param>
- public Task<bool> AsyncGet (IDisposable bridge, CancellationToken token)
- {
- linkBridge = bridge;
- cancellationRegistration = token.Register (() =>
- {
- completion.TrySetCanceled ();
- CompletionSet ();
- });
-
- return completion.Task;
- }
-
- /// <summary>
- /// Called after the result has been set,
- /// cleans up after this block.
- /// </summary>
- void CompletionSet ()
- {
- if (linkBridge != null) {
- linkBridge.Dispose ();
- linkBridge = null;
- }
-
- cancellationRegistration.Dispose ();
- }
-
- public Task Completion {
- get { throw new NotSupportedException (); }
- }
-
- public void Complete ()
- {
- completion.TrySetResult (false);
- CompletionSet ();
- }
-
- public void Fault (Exception exception)
- {
- Complete ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// PassingMessageBox.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Message box for blocks that don't need any special processing of incoming items.
- /// </summary>
- class PassingMessageBox<TInput> : MessageBox<TInput> {
- readonly Action<bool> processQueue;
-
- public PassingMessageBox (
- ITargetBlock<TInput> target, BlockingCollection<TInput> messageQueue,
- CompletionHelper compHelper, Func<bool> externalCompleteTester,
- Action<bool> processQueue, DataflowBlockOptions dataflowBlockOptions,
- bool greedy = true, Func<bool> canAccept = null)
- : base (target, messageQueue, compHelper, externalCompleteTester,
- dataflowBlockOptions, greedy, canAccept)
- {
- this.processQueue = processQueue;
- }
-
- /// <summary>
- /// Makes sure the input queue is processed the way it needs to.
- /// Executes synchronously, so shouldn't cause any long processing.
- /// </summary>
- /// <param name="newItem">Was new item just added?</param>
- protected override void EnsureProcessing (bool newItem)
- {
- processQueue (newItem);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// PredicateBlock.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// This block is used by the version of <see cref="DataflowBlock.LinkTo"/>
- /// that has a predicate to wrap the target block,
- /// so that the predicate can be checked.
- /// </summary>
- class PredicateBlock<T> : ITargetBlock<T> {
- /// <summary>
- /// Wraps the source block of the link.
- /// This is necessary so that the communication from target to source works correctly.
- /// </summary>
- class SourceBlock : ISourceBlock<T> {
- readonly ISourceBlock<T> actualSource;
- readonly PredicateBlock<T> predicateBlock;
-
- public SourceBlock (ISourceBlock<T> actualSource,
- PredicateBlock<T> predicateBlock)
- {
- this.actualSource = actualSource;
- this.predicateBlock = predicateBlock;
- }
-
- public Task Completion
- {
- get { return actualSource.Completion; }
- }
-
- public void Complete ()
- {
- actualSource.Complete ();
- }
-
- public void Fault (Exception exception)
- {
- actualSource.Fault (exception);
- }
-
- public T ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target, out bool messageConsumed)
- {
- return actualSource.ConsumeMessage (messageHeader, predicateBlock,
- out messageConsumed);
- }
-
- public IDisposable LinkTo (ITargetBlock<T> target,
- DataflowLinkOptions linkOptions)
- {
- return actualSource.LinkTo (target, linkOptions);
- }
-
- public void ReleaseReservation (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- actualSource.ReleaseReservation (messageHeader, predicateBlock);
- }
-
- public bool ReserveMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- return actualSource.ReserveMessage (messageHeader, predicateBlock);
- }
- }
-
- readonly ITargetBlock<T> actualTarget;
- readonly Predicate<T> predicate;
- readonly SourceBlock sourceBlock;
-
- public PredicateBlock (ISourceBlock<T> actualSource,
- ITargetBlock<T> actualTarget, Predicate<T> predicate)
- {
- this.actualTarget = actualTarget;
- this.predicate = predicate;
- sourceBlock = new SourceBlock (actualSource, this);
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source,
- bool consumeToAccept)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (consumeToAccept && source == null)
- throw new ArgumentException (
- "consumeToAccept may only be true if provided with a non-null source.",
- "consumeToAccept");
-
- if (!predicate(messageValue))
- return DataflowMessageStatus.Declined;
-
- return actualTarget.OfferMessage (messageHeader, messageValue, sourceBlock,
- consumeToAccept);
- }
-
- public Task Completion {
- get { return actualTarget.Completion; }
- }
-
- public void Complete ()
- {
- actualTarget.Complete ();
- }
-
- public void Fault (Exception exception)
- {
- actualTarget.Fault (exception);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// PropagatorWrapperBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Block returned by <see cref="DataflowBlock.Encapsulate{TInput,TOutput}"/>.
- /// </summary>
- class PropagatorWrapperBlock<TInput, TOutput> :
- IPropagatorBlock<TInput, TOutput> {
- readonly ITargetBlock<TInput> targetBlock;
- readonly ISourceBlock<TOutput> sourceBlock;
-
- public PropagatorWrapperBlock (
- ITargetBlock<TInput> target, ISourceBlock<TOutput> source)
- {
- if (target == null)
- throw new ArgumentNullException ("target");
- if (source == null)
- throw new ArgumentNullException ("source");
-
- targetBlock = target;
- sourceBlock = source;
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept)
- {
- return targetBlock.OfferMessage (
- messageHeader, messageValue, source, consumeToAccept);
- }
-
- public TOutput ConsumeMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target,
- out bool messageConsumed)
- {
- return sourceBlock.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- public IDisposable LinkTo (
- ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions)
- {
- return sourceBlock.LinkTo (target, linkOptions);
- }
-
- public void ReleaseReservation (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
- {
- sourceBlock.ReleaseReservation (messageHeader, target);
- }
-
- public bool ReserveMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
- {
- return sourceBlock.ReserveMessage (messageHeader, target);
- }
-
- public void Complete ()
- {
- targetBlock.Complete ();
- }
-
- public void Fault (Exception exception)
- {
- targetBlock.Fault (exception);
- }
-
- public Task Completion {
- get { return sourceBlock.Completion; }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// ReceiveBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// This internal block is used by the <see cref="DataflowBlock.Receive"/> methods
- /// to retrieve elements in either blocking or asynchronous way.
- /// </summary>
- class ReceiveBlock<TOutput> : ITargetBlock<TOutput> {
- readonly TaskCompletionSource<TOutput> completion =
- new TaskCompletionSource<TOutput> ();
-
- readonly CancellationToken token;
- CancellationTokenRegistration cancellationRegistration;
- readonly Timer timeoutTimer;
-
- IDisposable linkBridge;
-
- public ReceiveBlock (CancellationToken token, int timeout)
- {
- this.token = token;
- cancellationRegistration = token.Register (() =>
- {
- lock (completion) {
- completion.TrySetCanceled ();
- }
- CompletionSet ();
- });
- timeoutTimer = new Timer (
- _ =>
- {
- lock (completion) {
- completion.TrySetException (new TimeoutException ());
- }
- CompletionSet ();
- }, null, timeout,
- Timeout.Infinite);
- }
-
- public DataflowMessageStatus OfferMessage (
- DataflowMessageHeader messageHeader, TOutput messageValue,
- ISourceBlock<TOutput> source, bool consumeToAccept)
- {
- if (!messageHeader.IsValid)
- return DataflowMessageStatus.Declined;
-
- if (completion.Task.Status != TaskStatus.WaitingForActivation)
- return DataflowMessageStatus.DecliningPermanently;
-
- lock (completion) {
- if (completion.Task.Status != TaskStatus.WaitingForActivation)
- return DataflowMessageStatus.DecliningPermanently;
-
- if (consumeToAccept) {
- bool consummed;
- if (!source.ReserveMessage (messageHeader, this))
- return DataflowMessageStatus.NotAvailable;
- messageValue = source.ConsumeMessage (messageHeader, this, out consummed);
- if (!consummed)
- return DataflowMessageStatus.NotAvailable;
- }
-
- completion.TrySetResult (messageValue);
- }
- CompletionSet ();
- return DataflowMessageStatus.Accepted;
- }
-
- /// <summary>
- /// Synchronously waits until an item is available.
- /// </summary>
- /// <param name="bridge">The disposable object returned by <see cref="ISourceBlock{TOutput}.LinkTo"/>.</param>
- public TOutput WaitAndGet (IDisposable bridge)
- {
- try {
- return AsyncGet (bridge).Result;
- } catch (AggregateException e) {
- if (e.InnerException is TaskCanceledException)
- throw new OperationCanceledException (token);
- // resets the stack trace, but that shouldn't matter here
- throw e.InnerException;
- }
- }
-
- /// <summary>
- /// Asynchronously waits until an item is available.
- /// </summary>
- /// <param name="bridge">The disposable object returned by <see cref="ISourceBlock{TOutput}.LinkTo"/>.</param>
- public Task<TOutput> AsyncGet (IDisposable bridge)
- {
- linkBridge = bridge;
-
- return completion.Task;
- }
-
- /// <summary>
- /// Called after the result has been set,
- /// cleans up after this block.
- /// </summary>
- void CompletionSet ()
- {
- if (linkBridge != null) {
- linkBridge.Dispose ();
- linkBridge = null;
- }
-
- cancellationRegistration.Dispose ();
- timeoutTimer.Dispose ();
- }
-
- public Task Completion {
- get { throw new NotSupportedException (); }
- }
-
- public void Complete ()
- {
- lock (completion) {
- completion.TrySetException (new InvalidOperationException (
- "No item could be received from the source."));
- }
- CompletionSet ();
- }
-
- public void Fault (Exception exception)
- {
- Complete ();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// SendBlock.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// This block is used in <see cref="DataflowBlock.SendAsync"/>
- /// to asynchronously wait until a single item is sent to a given target.
- /// </summary>
- class SendBlock<T> : ISourceBlock<T> {
- readonly ITargetBlock<T> sendTarget;
- readonly T item;
- CancellationToken cancellationToken;
- readonly TaskCompletionSource<bool> taskCompletionSource =
- new TaskCompletionSource<bool> ();
- readonly DataflowMessageHeader sendHeader = new DataflowMessageHeader (1);
- CancellationTokenRegistration cancellationTokenRegistration;
-
- bool isReserved;
-
- volatile bool cancelDisabled;
-
- public SendBlock (ITargetBlock<T> sendTarget, T item,
- CancellationToken cancellationToken)
- {
- this.sendTarget = sendTarget;
- this.item = item;
- this.cancellationToken = cancellationToken;
- }
-
- /// <summary>
- /// Sends the item given in the constructor to the target block.
- /// </summary>
- /// <returns>Task that completes when the sending is done, or can't be performed.</returns>
- public Task<bool> Send ()
- {
- cancellationTokenRegistration = cancellationToken.Register (
- () =>
- {
- if (!cancelDisabled)
- taskCompletionSource.SetCanceled ();
- });
-
- PerformSend ();
-
- return taskCompletionSource.Task;
- }
-
- /// <summary>
- /// Offers the item to the target and hadles its response.
- /// </summary>
- void PerformSend ()
- {
- DisableCancel ();
-
- if (taskCompletionSource.Task.IsCanceled)
- return;
-
- var status = sendTarget.OfferMessage (sendHeader, item, this, false);
-
- if (status == DataflowMessageStatus.Accepted)
- SetResult (true);
- else if (status != DataflowMessageStatus.Postponed)
- SetResult (false);
- else
- EnableCancel ();
- }
-
- public Task Completion {
- get { throw new NotSupportedException (); }
- }
-
- public void Complete ()
- {
- throw new NotSupportedException ();
- }
-
- public void Fault (Exception exception)
- {
- throw new NotSupportedException ();
- }
-
- public T ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target, out bool messageConsumed)
- {
- if (!messageHeader.IsValid)
- throw new ArgumentException ("The messageHeader is not valid.",
- "messageHeader");
- if (target == null)
- throw new ArgumentNullException("target");
-
- DisableCancel ();
-
- messageConsumed = false;
-
- if (taskCompletionSource.Task.IsCanceled)
- return default(T);
-
- if (messageHeader != sendHeader || target != sendTarget) {
- EnableCancel ();
- return default(T);
- }
-
- SetResult (true);
-
- messageConsumed = true;
- return item;
- }
-
- public IDisposable LinkTo (ITargetBlock<T> target, DataflowLinkOptions linkOptions)
- {
- throw new NotSupportedException ();
- }
-
- public void ReleaseReservation (DataflowMessageHeader messageHeader, ITargetBlock<T> target)
- {
- if (messageHeader != sendHeader || target != sendTarget || !isReserved)
- throw new InvalidOperationException (
- "The target did not have the message reserved.");
-
- isReserved = false;
- EnableCancel ();
- PerformSend ();
- }
-
- public bool ReserveMessage (DataflowMessageHeader messageHeader, ITargetBlock<T> target)
- {
- DisableCancel ();
-
- if (messageHeader == sendHeader && target == sendTarget) {
- isReserved = true;
- return true;
- }
-
- EnableCancel ();
-
- return false;
- }
-
- /// <summary>
- /// Temporarily disables cancelling.
- /// </summary>
- void DisableCancel ()
- {
- cancelDisabled = true;
- }
-
- /// <summary>
- /// Enables cancelling after it was disabled.
- /// If cancellation was attempted in the meantime,
- /// actually performs the cancelling.
- /// </summary>
- void EnableCancel ()
- {
- cancelDisabled = false;
-
- if (cancellationToken.IsCancellationRequested)
- taskCompletionSource.SetCanceled ();
- }
-
- /// <summary>
- /// Sets the result of the operation.
- /// </summary>
- void SetResult (bool result)
- {
- cancellationTokenRegistration.Dispose ();
- taskCompletionSource.SetResult (result);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// TargetCollection.cs
-//
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-
-namespace System.Threading.Tasks.Dataflow {
- /// <summary>
- /// Base class for collection of target blocks for a source block.
- /// Also handles sending messages to the target blocks.
- /// </summary>
- abstract class TargetCollectionBase<T> {
- /// <summary>
- /// Represents a target block with its options.
- /// </summary>
- protected class Target : IDisposable {
- readonly TargetCollectionBase<T> targetCollection;
- volatile int remainingMessages;
- readonly CancellationTokenSource cancellationTokenSource;
-
- public ITargetBlock<T> TargetBlock { get; private set; }
-
- public Target (TargetCollectionBase<T> targetCollection,
- ITargetBlock<T> targetBlock, int maxMessages,
- CancellationTokenSource cancellationTokenSource)
- {
- TargetBlock = targetBlock;
- this.targetCollection = targetCollection;
- remainingMessages = maxMessages;
- this.cancellationTokenSource = cancellationTokenSource;
-
- Postponed = new AtomicBoolean ();
- Reserved = new AtomicBoolean ();
- }
-
- /// <summary>
- /// Is called after a message was sent, makes sure the linked is destroyed after
- /// <see cref="DataflowLinkOptions.MaxMessages"/> were sent.
- /// </summary>
- public void MessageSent()
- {
- if (remainingMessages != -1)
- remainingMessages--;
- if (remainingMessages == 0)
- Dispose ();
- }
-
- readonly AtomicBoolean disabled = new AtomicBoolean ();
- /// <summary>
- /// Is the link destroyed?
- /// </summary>
- public bool Disabled
- {
- get { return disabled.Value; }
- }
-
- /// <summary>
- /// Destroys the link to this target.
- /// </summary>
- public void Dispose ()
- {
- disabled.Value = true;
-
- if (cancellationTokenSource != null)
- cancellationTokenSource.Cancel ();
-
- Target ignored;
- targetCollection.TargetDictionary.TryRemove (TargetBlock, out ignored);
-
- // to avoid memory leak; it could take a long time
- // before this object is actually removed from the collection
- TargetBlock = null;
- }
-
- /// <summary>
- /// Does this target have a postponed message?
- /// </summary>
- public AtomicBoolean Postponed { get; private set; }
-
- /// <summary>
- /// Does this target have a reserved message?
- /// </summary>
- /// <remarks>Used only by broadcast blocks.</remarks>
- public AtomicBoolean Reserved { get; private set; }
- }
-
- readonly ISourceBlock<T> block;
- readonly bool broadcast;
- readonly bool consumeToAccept;
-
- readonly ConcurrentQueue<Target> prependQueue = new ConcurrentQueue<Target> ();
- readonly ConcurrentQueue<Target> appendQueue = new ConcurrentQueue<Target> ();
- readonly LinkedList<Target> targets = new LinkedList<Target> ();
-
- protected readonly ConcurrentDictionary<ITargetBlock<T>, Target> TargetDictionary =
- new ConcurrentDictionary<ITargetBlock<T>, Target> ();
-
- // lastMessageHeaderId will be always accessed only from one thread
- long lastMessageHeaderId;
- // currentMessageHeaderId can be read from multiple threads at the same time
- long currentMessageHeaderId;
-
- bool firstOffering;
- T currentItem;
-
- protected TargetCollectionBase (ISourceBlock<T> block, bool broadcast, bool consumeToAccept)
- {
- this.block = block;
- this.broadcast = broadcast;
- this.consumeToAccept = consumeToAccept;
- }
-
- /// <summary>
- /// Adds a target block to send messages to.
- /// </summary>
- /// <returns>
- /// An object that can be used to destroy the link to the added target.
- /// </returns>
- public IDisposable AddTarget (ITargetBlock<T> targetBlock, DataflowLinkOptions options)
- {
- CancellationTokenSource cancellationTokenSource = null;
- if (options.PropagateCompletion) {
- cancellationTokenSource = new CancellationTokenSource();
- block.Completion.ContinueWith (t =>
- {
- if (t.IsFaulted)
- targetBlock.Fault (t.Exception);
- else
- targetBlock.Complete ();
- }, cancellationTokenSource.Token);
- }
-
- var target = new Target (
- this, targetBlock, options.MaxMessages, cancellationTokenSource);
- TargetDictionary [targetBlock] = target;
- if (options.Append)
- appendQueue.Enqueue (target);
- else
- prependQueue.Enqueue (target);
-
- return target;
- }
-
- /// <summary>
- /// Sets the current item to be offered to targets
- /// </summary>
- public void SetCurrentItem (T item)
- {
- firstOffering = true;
- currentItem = item;
- Volatile.Write (ref currentMessageHeaderId, ++lastMessageHeaderId);
-
- ClearUnpostponed ();
- }
-
- /// <summary>
- /// Clears the collection of "unpostponed" targets.
- /// </summary>
- protected abstract void ClearUnpostponed ();
-
- /// <summary>
- /// Resets the current item to be offered to targets.
- /// This means there is currently nothing to offer.
- /// </summary>
- public void ResetCurrentItem ()
- {
- currentItem = default(T);
- Volatile.Write (ref currentMessageHeaderId, 0);
- }
-
- /// <summary>
- /// Is there an item to send right now?
- /// </summary>
- public bool HasCurrentItem {
- get { return Volatile.Read (ref currentMessageHeaderId) != 0; }
- }
-
- /// <summary>
- /// Offers the current item to all eligible targets.
- /// </summary>
- /// <returns>Was the item accepted? (Always <c>false</c> for broadcast blocks.)</returns>
- public bool OfferItemToTargets ()
- {
- // is there an item to offer?
- if (!HasCurrentItem)
- return false;
-
- var old = Tuple.Create (targets.First, targets.Last);
-
- do {
- // order is important here, we want to make sure that prepended target
- // added before appended target is processed first
- var appended = PrependOrAppend (false);
- var prepended = PrependOrAppend (true);
-
- if (OfferItemToTargets (prepended))
- return true;
-
- if (firstOffering) {
- if (OfferItemToTargets (old))
- return true;
- firstOffering = false;
- } else {
- if (OfferItemToUnpostponed ())
- return true;
- }
-
- if (OfferItemToTargets (appended))
- return true;
- } while (NeedsProcessing);
-
- return false;
- }
-
- /// <summary>
- /// Are there any targets that currently require a message to be sent to them?
- /// </summary>
- public bool NeedsProcessing {
- get {
- return !appendQueue.IsEmpty || !prependQueue.IsEmpty
- || !UnpostponedIsEmpty;
- }
- }
-
- /// <summary>
- /// Is the collection of unpostponed targets empty?
- /// </summary>
- protected abstract bool UnpostponedIsEmpty { get; }
-
- /// <summary>
- /// Prepends (appends) targets that should be prepended (appended) to the collection of targets.
- /// </summary>
- /// <param name="prepend"><c>true</c> to prepend, <c>false</c> to append.</param>
- /// <returns>
- /// Nodes that contain first and last target added to the list,
- /// or <c>null</c> if no nodes were added.
- /// </returns>
- Tuple<LinkedListNode<Target>, LinkedListNode<Target>> PrependOrAppend (
- bool prepend)
- {
- var queue = prepend ? prependQueue : appendQueue;
-
- if (queue.IsEmpty)
- return null;
-
- LinkedListNode<Target> first = null;
- LinkedListNode<Target> last = null;
-
- Target target;
- while (queue.TryDequeue (out target)) {
- var node = prepend
- ? targets.AddFirst (target)
- : targets.AddLast (target);
- if (first == null)
- first = node;
- last = node;
- }
-
- return prepend
- ? Tuple.Create (last, first)
- : Tuple.Create (first, last);
- }
-
- /// <summary>
- /// Offers the current item to the targets between the given nodes (inclusive).
- /// </summary>
- /// <returns>Was the item accepted? (Always <c>false</c> for broadcast blocks.)</returns>
- bool OfferItemToTargets (
- Tuple<LinkedListNode<Target>, LinkedListNode<Target>> targetPair)
- {
- if (targetPair == null
- || targetPair.Item1 == null || targetPair.Item2 == null)
- return false;
-
- var node = targetPair.Item1;
- while (node != targetPair.Item2.Next) {
- if (node.Value.Disabled) {
- var nodeToRemove = node;
- node = node.Next;
- targets.Remove (nodeToRemove);
- continue;
- }
-
- if (OfferItem (node.Value) && !broadcast)
- return true;
-
- node = node.Next;
- }
-
- return false;
- }
-
- /// <summary>
- /// Offers the current item to unpostponed targets.
- /// </summary>
- /// <returns>Was the item accepted? (Always <c>false</c> for broadcast blocks.)</returns>
- protected abstract bool OfferItemToUnpostponed ();
-
- /// <summary>
- /// Offers the current item to the given target.
- /// </summary>
- /// <returns>Was the item accepted?</returns>
- protected bool OfferItem (Target target)
- {
- if (target.Reserved.Value)
- return false;
- if (!broadcast && target.Postponed.Value)
- return false;
-
- var result = target.TargetBlock.OfferMessage (
- // volatile read is not necessary here,
- // because currentMessageHeaderId is always written from this thread
- new DataflowMessageHeader (currentMessageHeaderId), currentItem, block,
- consumeToAccept);
-
- switch (result) {
- case DataflowMessageStatus.Accepted:
- target.MessageSent ();
- return true;
- case DataflowMessageStatus.Postponed:
- target.Postponed.Value = true;
- return false;
- case DataflowMessageStatus.DecliningPermanently:
- target.Dispose ();
- return false;
- default:
- return false;
- }
- }
-
- /// <summary>
- /// Returns whether the given header corresponds to the current item.
- /// </summary>
- public bool VerifyHeader (DataflowMessageHeader header)
- {
- return header.Id == Volatile.Read (ref currentMessageHeaderId);
- }
- }
-
- /// <summary>
- /// Target collection for non-broadcast blocks.
- /// </summary>
- class TargetCollection<T> : TargetCollectionBase<T> {
- readonly ConcurrentQueue<Target> unpostponedTargets =
- new ConcurrentQueue<Target> ();
-
- public TargetCollection (ISourceBlock<T> block)
- : base (block, false, false)
- {
- }
-
- /// <summary>
- /// Is the collection of unpostponed targets empty?
- /// </summary>
- protected override bool UnpostponedIsEmpty {
- get { return unpostponedTargets.IsEmpty; }
- }
-
- /// <summary>
- /// Returns whether the given header corresponds to the current item
- /// and that the given target block postponed this item.
- /// </summary>
- public bool VerifyHeader (DataflowMessageHeader header, ITargetBlock<T> targetBlock)
- {
- return VerifyHeader (header)
- && TargetDictionary[targetBlock].Postponed.Value;
- }
-
- /// <summary>
- /// Unpostpones the given target.
- /// </summary>
- /// <param name="targetBlock">Target to unpostpone.</param>
- /// <param name="messageConsumed">Did the target consume an item?</param>
- public void UnpostponeTarget (ITargetBlock<T> targetBlock, bool messageConsumed)
- {
- Target target;
- if (!TargetDictionary.TryGetValue (targetBlock, out target))
- return;
-
- if (messageConsumed)
- target.MessageSent ();
- unpostponedTargets.Enqueue (target);
-
- target.Postponed.Value = false;
- }
-
- /// <summary>
- /// Clears the collection of "unpostponed" targets.
- /// </summary>
- protected override void ClearUnpostponed ()
- {
- Target ignored;
- while (unpostponedTargets.TryDequeue (out ignored)) {
- }
- }
-
- /// <summary>
- /// Offers the current item to unpostponed targets.
- /// </summary>
- /// <returns>Was the item accepted?</returns>
- protected override bool OfferItemToUnpostponed ()
- {
- Target target;
- while (unpostponedTargets.TryDequeue (out target)) {
- if (!target.Disabled && OfferItem (target))
- return true;
- }
-
- return false;
- }
- }
-
- /// <summary>
- /// Target collection for broadcast blocks.
- /// </summary>
- class BroadcastTargetCollection<T> : TargetCollectionBase<T> {
- // it's necessary to store the headers because of a race between
- // UnpostponeTargetConsumed and SetCurrentItem
- readonly ConcurrentQueue<Tuple<Target, DataflowMessageHeader>>
- unpostponedTargets =
- new ConcurrentQueue<Tuple<Target, DataflowMessageHeader>> ();
-
- public BroadcastTargetCollection (ISourceBlock<T> block, bool consumeToAccept)
- : base (block, true, consumeToAccept)
- {
- }
-
- /// <summary>
- /// Is the collection of unpostponed targets empty?
- /// </summary>
- protected override bool UnpostponedIsEmpty {
- get { return unpostponedTargets.IsEmpty; }
- }
-
- /// <summary>
- /// Marks the target as having a reserved message.
- /// </summary>
- public void ReserveTarget (ITargetBlock<T> targetBlock)
- {
- TargetDictionary [targetBlock].Reserved.Value = true;
- }
-
- /// <summary>
- /// Unpostpone target after it consumed a message.
- /// </summary>
- /// <param name="targetBlock">The target to unpostpone.</param>
- /// <param name="header">Header of the message the target consumed.</param>
- public void UnpostponeTargetConsumed (ITargetBlock<T> targetBlock,
- DataflowMessageHeader header)
- {
- Target target = TargetDictionary [targetBlock];
-
- target.MessageSent ();
- unpostponedTargets.Enqueue (Tuple.Create (target, header));
-
- target.Postponed.Value = false;
- target.Reserved.Value = false;
- }
-
- /// <summary>
- /// Unpostpone target in the case when it didn't successfuly consume a message.
- /// </summary>
- public void UnpostponeTargetNotConsumed (ITargetBlock<T> targetBlock)
- {
- Target target;
- if (!TargetDictionary.TryGetValue (targetBlock, out target))
- return;
-
- unpostponedTargets.Enqueue (Tuple.Create (target,
- new DataflowMessageHeader ()));
-
- target.Postponed.Value = false;
- target.Reserved.Value = false;
- }
-
- /// <summary>
- /// Clears the collection of "unpostponed" targets.
- /// </summary>
- protected override void ClearUnpostponed ()
- {
- Tuple<Target, DataflowMessageHeader> ignored;
- while (unpostponedTargets.TryDequeue (out ignored)) {
- }
- }
-
- /// <summary>
- /// Offers the current item to unpostponed targets.
- /// </summary>
- /// <returns>Always <c>false</c>.</returns>
- protected override bool OfferItemToUnpostponed ()
- {
- Tuple<Target, DataflowMessageHeader> tuple;
- while (unpostponedTargets.TryDequeue (out tuple)) {
- // offer to unconditionaly unpostponed
- // and those that consumed some old value
- if (!tuple.Item1.Disabled
- && (!tuple.Item2.IsValid || !VerifyHeader (tuple.Item2)))
- OfferItem (tuple.Item1);
- }
-
- return false;
- }
- }
-}
+++ /dev/null
-// TransformBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class TransformBlock<TInput, TOutput> :
- IPropagatorBlock<TInput, TOutput>, IReceivableSourceBlock<TOutput> {
- readonly ExecutionDataflowBlockOptions dataflowBlockOptions;
- readonly CompletionHelper compHelper;
- readonly BlockingCollection<TInput> messageQueue = new BlockingCollection<TInput> ();
- readonly MessageBox<TInput> messageBox;
- readonly OutgoingQueue<TOutput> outgoing;
- readonly Func<TInput, TOutput> transform;
- readonly Func<TInput, Task<TOutput>> asyncTransform;
-
- TransformBlock (ExecutionDataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = new CompletionHelper (dataflowBlockOptions);
- }
-
- public TransformBlock (Func<TInput, TOutput> transform)
- : this (transform, ExecutionDataflowBlockOptions.Default)
- {
- }
-
- public TransformBlock (Func<TInput, TOutput> transform,
- ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this (dataflowBlockOptions)
- {
- if (transform == null)
- throw new ArgumentNullException("transform");
-
- this.transform = transform;
- this.messageBox = new ExecutingMessageBox<TInput> (
- this, messageQueue, compHelper,
- () => outgoing.IsCompleted, TransformProcess, () => outgoing.Complete (),
- dataflowBlockOptions);
- this.outgoing = new OutgoingQueue<TOutput> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions);
- }
-
- public TransformBlock(Func<TInput, Task<TOutput>> transform)
- : this(transform, ExecutionDataflowBlockOptions.Default)
- {
- }
-
- public TransformBlock (Func<TInput, Task<TOutput>> transform,
- ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this (dataflowBlockOptions)
- {
- if (transform == null)
- throw new ArgumentNullException("transform");
-
- this.asyncTransform = transform;
- this.messageBox = new AsyncExecutingMessageBox<TInput, Task<TOutput>> (
- this, messageQueue, compHelper, () => outgoing.IsCompleted,
- AsyncTransformProcess, AsyncProcessFinishedTask, () => outgoing.Complete (),
- dataflowBlockOptions);
- this.outgoing = new OutgoingQueue<TOutput> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions);
- }
-
- DataflowMessageStatus ITargetBlock<TInput>.OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept)
- {
- return messageBox.OfferMessage (messageHeader, messageValue, source, consumeToAccept);
- }
-
- public IDisposable LinkTo (ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- TOutput ISourceBlock<TOutput>.ConsumeMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- void ISourceBlock<TOutput>.ReleaseReservation (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<TOutput>.ReserveMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<TOutput> filter, out TOutput item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<TOutput> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- /// <summary>
- /// Transforms one item from the queue if the transform delegate is synchronous.
- /// </summary>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue is empty.</returns>
- bool TransformProcess ()
- {
- TInput input;
-
- var dequeued = messageQueue.TryTake (out input);
- if (dequeued)
- outgoing.AddData (transform (input));
-
- return dequeued;
- }
-
- /// <summary>
- /// Processes one item from the queue if the transform delegate is asynchronous.
- /// </summary>
- /// <param name="task">The Task that was returned by the synchronous part of the delegate.</param>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue was empty.</returns>
- bool AsyncTransformProcess (out Task<TOutput> task)
- {
- TInput input;
-
- var dequeued = messageQueue.TryTake (out input);
- if (dequeued)
- task = asyncTransform (input);
- else
- task = null;
-
- return dequeued;
- }
-
- /// <summary>
- /// Process result of finished asynchronous transformation.
- /// </summary>
- void AsyncProcessFinishedTask (Task<TOutput> task)
- {
- if (task == null || task.IsCanceled)
- messageBox.DecreaseCount ();
- else
- outgoing.AddData (task.Result);
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public int InputCount {
- get { return messageQueue.Count; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// TransformManyBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class TransformManyBlock<TInput, TOutput> :
- IPropagatorBlock<TInput, TOutput>, IReceivableSourceBlock<TOutput> {
- readonly CompletionHelper compHelper;
- readonly BlockingCollection<TInput> messageQueue = new BlockingCollection<TInput> ();
- readonly MessageBox<TInput> messageBox;
- readonly ExecutionDataflowBlockOptions dataflowBlockOptions;
- readonly Func<TInput, IEnumerable<TOutput>> transform;
- readonly Func<TInput, Task<IEnumerable<TOutput>>> asyncTransform;
- readonly OutgoingQueue<TOutput> outgoing;
-
- TransformManyBlock (ExecutionDataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = new CompletionHelper (dataflowBlockOptions);
- }
-
- public TransformManyBlock (Func<TInput, IEnumerable<TOutput>> transform)
- : this (transform, ExecutionDataflowBlockOptions.Default)
- {
- }
-
- public TransformManyBlock (Func<TInput, IEnumerable<TOutput>> transform,
- ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this (dataflowBlockOptions)
- {
- if (transform == null)
- throw new ArgumentNullException ("transform");
-
- this.transform = transform;
- this.messageBox = new ExecutingMessageBox<TInput> (this, messageQueue, compHelper,
- () => outgoing.IsCompleted, TransformProcess, () => outgoing.Complete (),
- dataflowBlockOptions);
- this.outgoing = new OutgoingQueue<TOutput> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions);
- }
-
- public TransformManyBlock (Func<TInput, Task<IEnumerable<TOutput>>> transform)
- : this (transform, ExecutionDataflowBlockOptions.Default)
- {
- }
-
- public TransformManyBlock (Func<TInput, Task<IEnumerable<TOutput>>> transform,
- ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this (dataflowBlockOptions)
- {
- if (transform == null)
- throw new ArgumentNullException ("transform");
-
- this.asyncTransform = transform;
- this.messageBox = new AsyncExecutingMessageBox<TInput, Task<IEnumerable<TOutput>>> (this, messageQueue, compHelper,
- () => outgoing.IsCompleted, AsyncTransformProcess, ProcessFinishedTask, () => outgoing.Complete (),
- dataflowBlockOptions);
- this.outgoing = new OutgoingQueue<TOutput> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions);
- }
-
- DataflowMessageStatus ITargetBlock<TInput>.OfferMessage (
- DataflowMessageHeader messageHeader, TInput messageValue,
- ISourceBlock<TInput> source, bool consumeToAccept)
- {
- return messageBox.OfferMessage (messageHeader, messageValue, source, consumeToAccept);
- }
-
- public IDisposable LinkTo (ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- TOutput ISourceBlock<TOutput>.ConsumeMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target,
- out bool messageConsumed)
- {
- return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed);
- }
-
- void ISourceBlock<TOutput>.ReleaseReservation (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<TOutput>.ReserveMessage (
- DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<TOutput> filter, out TOutput item)
- {
- return outgoing.TryReceive (filter, out item);
- }
-
- public bool TryReceiveAll (out IList<TOutput> items)
- {
- return outgoing.TryReceiveAll (out items);
- }
-
- /// <summary>
- /// Transforms one item from the queue if the transform delegate is synchronous.
- /// </summary>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue is empty.</returns>
- bool TransformProcess ()
- {
- TInput input;
-
- var dequeued = messageQueue.TryTake (out input);
- if (dequeued) {
- var result = transform (input);
-
- EnqueueTransformed (result);
- }
-
- return dequeued;
- }
-
- /// <summary>
- /// Adds the transformed collection to the output queue.
- /// </summary>
- void EnqueueTransformed (IEnumerable<TOutput> transformed)
- {
- bool first = true;
- if (transformed != null) {
- foreach (var item in transformed) {
- if (first)
- first = false;
- else
- messageBox.IncreaseCount ();
- outgoing.AddData (item);
- }
- }
- if (first)
- messageBox.DecreaseCount ();
- }
-
- /// <summary>
- /// Processes one item from the queue if the transform delegate is asynchronous.
- /// </summary>
- /// <param name="task">The Task that was returned by the synchronous part of the delegate.</param>
- /// <returns>Returns whether an item was processed. Returns <c>false</c> if the queue was empty.</returns>
- bool AsyncTransformProcess (out Task<IEnumerable<TOutput>> task)
- {
- TInput input;
-
- var dequeued = messageQueue.TryTake (out input);
- if (dequeued)
- task = asyncTransform (input);
- else
- task = null;
-
- return dequeued;
- }
-
- /// <summary>
- /// Process result of finished asynchronous transformation.
- /// </summary>
- void ProcessFinishedTask (Task<IEnumerable<TOutput>> task)
- {
- if (task == null || task.IsCanceled)
- messageBox.DecreaseCount ();
- else
- EnqueueTransformed (task.Result);
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- public int OutputCount {
- get { return outgoing.Count; }
- }
-
- public int InputCount {
- get { return messageQueue.Count; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-// WriteOnceBlock.cs
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-// Copyright (c) 2012 Petr Onderka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-namespace System.Threading.Tasks.Dataflow {
- public sealed class WriteOnceBlock<T> : IPropagatorBlock<T, T>, IReceivableSourceBlock<T> {
- readonly CompletionHelper compHelper;
- readonly BlockingCollection<T> messageQueue = new BlockingCollection<T> ();
- readonly MessageBox<T> messageBox;
- readonly DataflowBlockOptions dataflowBlockOptions;
- readonly Func<T, T> cloningFunction;
- readonly BroadcastOutgoingQueue<T> outgoing;
- AtomicBooleanValue written;
-
- public WriteOnceBlock (Func<T, T> cloningFunction)
- : this (cloningFunction, DataflowBlockOptions.Default)
- {
- }
-
- public WriteOnceBlock (Func<T, T> cloningFunction,
- DataflowBlockOptions dataflowBlockOptions)
- {
- if (dataflowBlockOptions == null)
- throw new ArgumentNullException ("dataflowBlockOptions");
-
- this.cloningFunction = cloningFunction;
- this.dataflowBlockOptions = dataflowBlockOptions;
- this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions);
- this.messageBox = new PassingMessageBox<T> (this, messageQueue, compHelper,
- () => true, _ => BroadcastProcess (), dataflowBlockOptions,
- canAccept: () => written.TrySet ());
- this.outgoing = new BroadcastOutgoingQueue<T> (this, compHelper,
- () => messageQueue.IsCompleted, messageBox.DecreaseCount,
- dataflowBlockOptions, cloningFunction != null);
- }
-
- DataflowMessageStatus ITargetBlock<T>.OfferMessage (
- DataflowMessageHeader messageHeader, T messageValue,
- ISourceBlock<T> source, bool consumeToAccept)
- {
- var result = messageBox.OfferMessage (messageHeader, messageValue, source,
- consumeToAccept);
- if (result == DataflowMessageStatus.Accepted)
- messageQueue.CompleteAdding ();
- return result;
- }
-
- public IDisposable LinkTo (ITargetBlock<T> target,
- DataflowLinkOptions linkOptions)
- {
- return outgoing.AddTarget (target, linkOptions);
- }
-
- T ISourceBlock<T>.ConsumeMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target,
- out bool messageConsumed)
- {
- T message = outgoing.ConsumeMessage (
- messageHeader, target, out messageConsumed);
- if (messageConsumed && cloningFunction != null)
- message = cloningFunction (message);
- return message;
- }
-
- void ISourceBlock<T>.ReleaseReservation (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- outgoing.ReleaseReservation (messageHeader, target);
- }
-
- bool ISourceBlock<T>.ReserveMessage (DataflowMessageHeader messageHeader,
- ITargetBlock<T> target)
- {
- return outgoing.ReserveMessage (messageHeader, target);
- }
-
- public bool TryReceive (Predicate<T> filter, out T item)
- {
- var received = outgoing.TryReceive (filter, out item);
- if (received && cloningFunction != null)
- item = cloningFunction (item);
- return received;
- }
-
- bool IReceivableSourceBlock<T>.TryReceiveAll (out IList<T> items)
- {
- T item;
- if (!TryReceive (null, out item)) {
- items = null;
- return false;
- }
-
- items = new[] { item };
- return true;
- }
-
- /// <summary>
- /// Moves an item from the input queue to the output queue.
- /// </summary>
- void BroadcastProcess ()
- {
- T item;
- if (messageQueue.TryTake (out item))
- outgoing.AddData (item);
- outgoing.DequeueItem ();
- }
-
- public void Complete ()
- {
- messageBox.Complete ();
- outgoing.Complete ();
- }
-
- void IDataflowBlock.Fault (Exception exception)
- {
- compHelper.RequestFault (exception);
- }
-
- public Task Completion {
- get { return compHelper.Completion; }
- }
-
- public override string ToString ()
- {
- return NameHelper.GetName (this, dataflowBlockOptions);
- }
- }
-}
\ No newline at end of file
AssertEx.cs
Blocks.cs
System.Threading.Tasks.Dataflow/DataflowMessageHeaderTest.cs
-System.Threading.Tasks.Dataflow/CompletionHelperTest.cs
System.Threading.Tasks.Dataflow/CompletionTest.cs
System.Threading.Tasks.Dataflow/ReceivingTest.cs
System.Threading.Tasks.Dataflow/OptionsTest.cs
System.Threading.Tasks.Dataflow/OutputAvailableTest.cs
System.Threading.Tasks.Dataflow/EncapsulateTest.cs
System.Threading.Tasks.Dataflow/ChooseTest.cs
-../System.Threading.Tasks.Dataflow/CompletionHelper.cs
../../Mono.Parallel/Mono.Threading/AtomicBoolean.cs
+++ /dev/null
-//
-// CompletionHelperTest.cs
-//
-// Author:
-// Jérémie "garuma" Laval <jeremie.laval@gmail.com>
-//
-// Copyright (c) 2011 Jérémie "garuma" Laval
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Threading.Tasks.Dataflow;
-
-using NUnit.Framework;
-
-namespace MonoTests.System.Threading.Tasks.Dataflow
-{
- [TestFixture]
- public class CompletionHelperTest
- {
- CompletionHelper helper;
-
- [SetUp]
- public void Setup ()
- {
- helper = CompletionHelper.GetNew (null);
- }
-
- [Test]
- public void InitialStateTest ()
- {
- Task completed = helper.Completion;
-
- Assert.IsNotNull (completed);
- Assert.IsFalse (completed.IsCompleted);
- }
-
- [Test]
- public void FaultedTest ()
- {
- Exception ex = new ApplicationException ("Foobar");
- helper.RequestFault (ex);
- Task completed = helper.Completion;
-
- Assert.IsNotNull (completed);
- Assert.IsTrue (completed.IsCompleted);
- Assert.AreEqual (TaskStatus.Faulted, completed.Status);
- Assert.AreEqual (ex, completed.Exception.InnerExceptions.First ());
- }
-
- [Test]
- public void CompleteTest ()
- {
- helper.Complete ();
- Task completed = helper.Completion;
-
- Assert.IsNotNull (completed);
- Assert.IsTrue (completed.IsCompleted);
- Assert.IsFalse (completed.IsFaulted);
- Assert.IsFalse (completed.IsCanceled);
- }
- }
-}