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