예제 #1
0
 /// <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.
 }
예제 #2
0
 /// <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)
 {
     Debug.Assert(_defaultTarget != null);
     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);
         }
     }
 }
예제 #3
0
        /// <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);
            }
        }
예제 #4
0
        /// <summary>Stores the next item using the reordering buffer.</summary>
        /// <param name="id">The ID of the item.</param>
        /// <param name="item">The async enumerable.</param>
        private async Task StoreOutputItemsReorderedAsync(long id, IAsyncEnumerable <TOutput>?item)
        {
            Debug.Assert(_reorderingBuffer is not null, "Expected a reordering buffer");
            Debug.Assert(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 is null)
            {
                _reorderingBuffer.AddItem(id, null, false);
                if (isBounded)
                {
                    target.ChangeBoundingCount(count: -1);
                }
                return;
            }

            // 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 (_reorderingBuffer.IsNext(id))
                {
                    await StoreOutputItemsNonReorderedWithIterationAsync(item).ConfigureAwait(false);

                    // here itemCopy remains null, so that base.AddItem will finish our interactions with the reordering buffer
                }
                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 = new List <TOutput>();
                        await foreach (TOutput element in item.ConfigureAwait(true))
                        {
                            itemCopy.Add(element);
                        }
                        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 is not null);
            }
        }