/// <summary>Attempts to retrieve the value for the first element in the queue.</summary> /// <param name="result">The value of the first element, if found.</param> /// <returns>true if an element was found; otherwise, false.</returns> public bool TryPeek( #if !NETSTANDARD2_0 [MaybeNullWhen(false)] #endif out T result) { // Starting with the head segment, look through all of the segments // for the first one we can find that's not empty. WaiterQueueSegment <T> s = _Head; while (true) { // Grab the next segment from this one, before we peek. // This is to be able to see whether the value has changed // during the peek operation. WaiterQueueSegment <T>?next = Volatile.Read(ref s.NextSegment); // Peek at the segment. If we find an element, we're done. if (s.TryPeek(out result !)) { return(true); } // The current segment was empty at the moment we checked. if (next != null) { // If prior to the peek there was already a next segment, then // during the peek no additional items could have been enqueued // to it and we can just move on to check the next segment. Debug.Assert(next == s.NextSegment); s = next; } else if (Volatile.Read(ref s.NextSegment) == null) { // The next segment is null. Nothing more to peek at. break; } // The next segment was null before we peeked but non-null after. // That means either when we peeked the first segment had // already been frozen but the new segment not yet added, // or that the first segment was empty and between the time // that we peeked and then checked _nextSegment, so many items // were enqueued that we filled the first segment and went // into the next. Since we need to peek in order, we simply // loop around again to peek on the same segment. The next // time around on this segment we'll then either successfully // peek or we'll find that next was non-null before peeking, // and we'll traverse to that segment. } result = default !;
//**************************************** /// <summary>Adds an object to the end of the <see cref="WaiterQueue{T}"/>.</summary> /// <param name="item">The object to add to the end of the <see cref="WaiterQueue{T}"/>. The value cannot be null</param> /// <returns>True if we're the first item in the queue, otherwise False</returns> public void Enqueue(T item) { if (item == null) { throw new ArgumentNullException(nameof(item)); } for (; ;) { var Tail = _Tail; // Try to append to the existing tail. if (Tail.TryEnqueue(item)) { return; } // If we were unsuccessful, take the lock so that we can compare and manipulate // the tail. Assuming another enqueuer hasn't already added a new segment, // do so, then loop around to try enqueueing again. lock (_CrossSegmentLock) { if (Tail == _Tail) { // Make sure no one else can enqueue to this segment. Tail.EnsureFrozenForEnqueues(); // We determine the new segment's length based on the old length. // In general, we double the size of the segment, to make it less likely // that we'll need to grow again. However, if the tail segment has a majority // of erased items, we leave the size as-is. var nextSize = (Tail.ErasedCount > Tail.Capacity / 2) ? Tail.Capacity : Math.Min(Tail.Capacity * 2, MaxSegmentLength); var newTail = new WaiterQueueSegment <T>(nextSize); // Hook up the new tail. Tail.NextSegment = newTail; _Tail = newTail; } } } }
//**************************************** /// <summary> /// Initializes a new instance of the <see cref="WaiterQueue{T}"/> class. /// </summary> public WaiterQueue() { _CrossSegmentLock = new object(); _Tail = _Head = new WaiterQueueSegment <T>(InitialSegmentLength); }