// 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 { /// Internal helper utilities. internal static class Common { /// /// 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. /// internal const long INVALID_REORDERING_ID = -1; /// A well-known message ID for code that will send exactly one message or /// where the exact message ID is not important. internal const int SINGLE_MESSAGE_ID = 1; /// A perf optimization for caching a well-known message header instead of /// constructing one every time it is needed. internal static readonly DataflowMessageHeader SingleMessageHeader = new DataflowMessageHeader(SINGLE_MESSAGE_ID); /// The cached completed Task{bool} with a result of true. internal static readonly Task CompletedTaskWithTrueResult = CreateCachedBooleanTask(true); /// The cached completed Task{bool} with a result of false. internal static readonly Task CompletedTaskWithFalseResult = CreateCachedBooleanTask(false); /// The cached completed TaskCompletionSource{VoidResult}. internal static readonly TaskCompletionSource CompletedVoidResultTaskCompletionSource = CreateCachedTaskCompletionSource(); /// Asserts that a given synchronization object is either held or not held. /// The monitor to check. /// Whether we want to assert that it's currently held or not held. [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."); } /// Keeping alive processing tasks: maximum number of processed messages. internal const int KEEP_ALIVE_NUMBER_OF_MESSAGES_THRESHOLD = 1; /// Keeping alive processing tasks: do not attempt this many times. internal const int KEEP_ALIVE_BAN_COUNT = 1000; /// A predicate type for TryKeepAliveUntil. /// Input state for the predicate in order to avoid closure allocations. /// Output state for the predicate in order to avoid closure allocations. /// The state of the predicate. internal delegate bool KeepAlivePredicate(TStateIn stateIn, out TStateOut stateOut); /// Actively waits for a predicate to become true. /// The predicate to become true. /// Input state for the predicate in order to avoid closure allocations. /// Output state for the predicate in order to avoid closure allocations. /// True if the predicate was evaluated and it returned true. False otherwise. internal static bool TryKeepAliveUntil(KeepAlivePredicate 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; } /// Unwraps an instance T from object state that is a WeakReference to that instance. /// The type of the data to be unwrapped. /// The weak reference. /// The T instance. internal static T UnwrapWeakReference(object state) where T : class { var wr = state as WeakReference; Debug.Assert(wr != null, "Expected a WeakReference as the state argument"); T item; return wr.TryGetTarget(out item) ? item : null; } /// Gets an ID for the dataflow block. /// The dataflow block. /// An ID for the dataflow block. 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; } /// Gets the name for the specified block, suitable to be rendered in a debugger window. /// The block for which a name is needed. /// /// The options to use when rendering the name. If no options are provided, the block's name is used directly. /// /// The name of the object. /// This is used from DebuggerDisplay attributes. [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; } } /// /// Gets whether the exception represents a cooperative cancellation acknowledgment. /// /// The exception to check. /// true if this exception represents a cooperative cancellation acknowledgment; otherwise, false. 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. } /// Registers a block for cancellation by completing when cancellation is requested. /// The block's cancellation token. /// The task that will complete when the block is completely done processing. /// An action that will decline permanently on the state passed to it. /// The block on which to decline permanently. internal static void WireCancellationToComplete( CancellationToken cancellationToken, Task completionTask, Action 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); } } /// Initializes the stack trace and watson bucket of an inactive exception. /// The exception to initialize. /// The initialized exception. [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; } } /// The name of the key in an Exception's Data collection used to store information on a dataflow message. internal const string EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE = "DataflowMessageValue"; // should not be localized /// Stores details on a dataflow message into an Exception's Data collection. /// Specifies the type of data stored in the message. /// The Exception whose Data collection should store message information. /// The message information to be stored. /// Whether to store the data into the exception's inner exception(s) in addition to the exception itself. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] internal static void StoreDataflowMessageValueIntoExceptionData(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); } } } /// Stores the specified string value into the specified key slot of the specified exception's data dictionary. /// The exception into which the key/value should be stored. /// The key. /// The value to be serialized as a string and stored. /// If the key is already present in the exception's data dictionary, the value is not overwritten. [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. } } /// Throws an exception asynchronously on the thread pool. /// The exception to throw. /// /// 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. /// internal static void ThrowAsync(Exception error) { ExceptionDispatchInfo edi = ExceptionDispatchInfo.Capture(error); ThreadPool.QueueUserWorkItem(state => { ((ExceptionDispatchInfo)state).Throw(); }, edi); } /// Adds the exception to the list, first initializing the list if the list is null. /// The list to add the exception to, and initialize if null. /// The exception to add or whose inner exception(s) should be added. /// Unwrap and add the inner exception(s) rather than the specified exception directly. /// This method is not thread-safe, in that it manipulates without any synchronization. internal static void AddException(ref List 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(); if (unwrapInnerExceptions) { AggregateException aggregate = exception as AggregateException; if (aggregate != null) { list.AddRange(aggregate.InnerExceptions); } else { list.Add(exception.InnerException); } } else list.Add(exception); } /// Creates a task we can cache for the desired Boolean result. /// The value of the Boolean. /// A task that may be cached. private static Task CreateCachedBooleanTask(bool value) { // AsyncTaskMethodBuilder 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.Create(); atmb.SetResult(value); return atmb.Task; // must be accessed after SetResult to get the cached task } /// Creates a TaskCompletionSource{T} completed with a value of default(T) that we can cache. /// Completed TaskCompletionSource{T} that may be cached. private static TaskCompletionSource CreateCachedTaskCompletionSource() { var tcs = new TaskCompletionSource(); tcs.SetResult(default(T)); return tcs; } /// Creates a task faulted with the specified exception. /// Specifies the type of the result for this task. /// The exception with which to complete the task. /// The faulted task. internal static Task CreateTaskFromException(Exception exception) { var atmb = System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create(); atmb.SetException(exception); return atmb.Task; } /// Creates a task canceled with the specified cancellation token. /// Specifies the type of the result for this task. /// The canceled task. [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] internal static Task CreateTaskFromCancellation(CancellationToken cancellationToken) { Contract.Requires(cancellationToken.IsCancellationRequested, "The task will only be immediately canceled if the token has cancellation requested already."); var t = new Task(CachedGenericDelegates.DefaultTResultFunc, cancellationToken); Debug.Assert(t.IsCanceled, "Task's constructor should cancel the task synchronously in the ctor."); return t; } /// Gets the completion task of a block, and protects against common cases of the completion task not being implemented or supported. /// The block. /// The completion task, or null if the block's completion task is not implemented or supported. 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; } /// /// Creates an IDisposable that, when disposed, will acquire the outgoing lock while removing /// the target block from the target registry. /// /// Specifies the type of data in the block. /// The outgoing lock used to protect the target registry. /// The target registry from which the target should be removed. /// The target to remove from the registry. /// An IDisposable that will unregister the target block from the registry while holding the outgoing lock. internal static IDisposable CreateUnlinker(object outgoingLock, TargetRegistry targetRegistry, ITargetBlock 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.CreateUnlinkerShimAction, outgoingLock, targetRegistry, targetBlock); } /// An infinite TimeSpan. internal static readonly TimeSpan InfiniteTimeSpan = Timeout.InfiniteTimeSpan; /// Validates that a timeout either is -1 or is non-negative and within the range of an Int32. /// The timeout to validate. /// true if the timeout is valid; otherwise, false. internal static bool IsValidTimeout(TimeSpan timeout) { long millisecondsTimeout = (long)timeout.TotalMilliseconds; return millisecondsTimeout >= Timeout.Infinite && millisecondsTimeout <= Int32.MaxValue; } /// Gets the options to use for continuation tasks. /// Any options to include in the result. /// The options to use. internal static TaskContinuationOptions GetContinuationOptions(TaskContinuationOptions toInclude = TaskContinuationOptions.None) { return toInclude | TaskContinuationOptions.DenyChildAttach; } /// Gets the options to use for tasks. /// If this task is being created to replace another. /// /// 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. /// /// The options to use. internal static TaskCreationOptions GetCreationOptionsForTask(bool isReplacementReplica = false) { TaskCreationOptions options = TaskCreationOptions.DenyChildAttach; if (isReplacementReplica) options |= TaskCreationOptions.PreferFairness; return options; } /// Starts an already constructed task with handling and observing exceptions that may come from the scheduling process. /// Task to be started. /// TaskScheduler to schedule the task on. /// null on success, an exception reference on scheduling error. In the latter case, the task reference is nulled out. 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); } /// Starts an already constructed task with handling and observing exceptions that may come from the scheduling process. /// Task to be started. /// TaskScheduler to schedule the task on. /// null on success, an exception reference on scheduling error. In the latter case, the task reference is nulled out. [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; } /// Pops and explicitly releases postponed messages after the block is done with processing. /// No locks should be held at this time. Unfortunately we cannot assert that. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] internal static void ReleaseAllPostponedMessages(ITargetBlock target, QueuedMap, DataflowMessageHeader> postponedMessages, ref List 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, 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."); } /// Cache ThrowAsync to avoid allocations when it is passed into PropagateCompletionXxx. internal static readonly Action AsyncExceptionHandler = ThrowAsync; /// /// Propagates completion of sourceCompletionTask to target synchronously. /// /// The task whose completion is to be propagated. It must be completed. /// The block where completion is propagated. /// Handler for exceptions from the target. May be null which would propagate the exception to the caller. internal static void PropagateCompletion(Task sourceCompletionTask, IDataflowBlock target, Action 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; } } /// /// Creates a continuation off sourceCompletionTask to complete target. See PropagateCompletion. /// 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); } /// /// Propagates completion of sourceCompletionTask to target based on sourceCompletionTask's current state. See PropagateCompletion. /// 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); } /// Static class used to cache generic delegates the C# compiler doesn't cache by default. /// Without this, we end up allocating the generic delegate each time the operation is used. static class CachedGenericDelegates { /// A function that returns the default value of T. internal readonly static Func DefaultTResultFunc = () => default(T); /// /// 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. /// internal readonly static Action, ITargetBlock> CreateUnlinkerShimAction = (syncObj, registry, target) => { lock (syncObj) registry.Remove(target); }; } } /// State used only when bounding. [DebuggerDisplay("BoundedCapacity={BoundedCapacity}}")] internal class BoundingState { /// The maximum number of messages allowed to be buffered. internal readonly int BoundedCapacity; /// The number of messages currently stored. /// /// 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. /// internal int CurrentCount; /// Initializes the BoundingState. /// The positive bounded capacity. internal BoundingState(int boundedCapacity) { Contract.Requires(boundedCapacity > 0, "Bounded is only supported with positive values."); BoundedCapacity = boundedCapacity; } /// Gets whether there's room available to add another message. internal bool CountIsLessThanBound { get { return CurrentCount < BoundedCapacity; } } } /// Stated used only when bounding and when postponed messages are stored. /// Specifies the type of input messages. [DebuggerDisplay("BoundedCapacity={BoundedCapacity}, PostponedMessages={PostponedMessagesCountForDebugger}")] internal class BoundingStateWithPostponed : BoundingState { /// Queue of postponed messages. internal readonly QueuedMap, DataflowMessageHeader> PostponedMessages = new QueuedMap, DataflowMessageHeader>(); /// /// The number of transfers from the postponement queue to the input queue currently being processed. /// /// /// 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. /// internal int OutstandingTransfers; /// Initializes the BoundingState. /// The positive bounded capacity. internal BoundingStateWithPostponed(int boundedCapacity) : base(boundedCapacity) { } /// Gets the number of postponed messages for the debugger. private int PostponedMessagesCountForDebugger { get { return PostponedMessages.Count; } } } /// Stated used only when bounding and when postponed messages and a task are stored. /// Specifies the type of input messages. internal class BoundingStateWithPostponedAndTask : BoundingStateWithPostponed { /// The task used to process messages. internal Task TaskForInputProcessing; /// Initializes the BoundingState. /// The positive bounded capacity. internal BoundingStateWithPostponedAndTask(int boundedCapacity) : base(boundedCapacity) { } } /// /// 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. /// [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] [DebuggerNonUserCode] internal struct VoidResult { } }