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