固定大小队列,在新的enques上自动将旧值出列

Xaq*_*ron 111 c# queue fifo

我正在使用ConcurrentQueue共享数据结构,其目的是保存传递给它的最后N个对象(历史记录类型).

假设我们有一个浏览器,我们希望最后100个浏览Urls.我想要一个队列,当容量变满时(历史中的100个地址),当新条目插入(入队)时自动删除(出列)最旧的(第一个)条目.

我怎样才能实现这个目标System.Collections

Ric*_*der 108

我会编写一个包装类,在Enqueue上检查Count,然后在计数超过限制时Dequeue.

 public class FixedSizedQueue<T>
 {
     ConcurrentQueue<T> q = new ConcurrentQueue<T>();
     private object lockObject = new object();

     public int Limit { get; set; }
     public void Enqueue(T obj)
     {
        q.Enqueue(obj);
        lock (lockObject)
        {
           T overflow;
           while (q.Count > Limit && q.TryDequeue(out overflow)) ;
        }
     }
 }
Run Code Online (Sandbox Code Playgroud)

  • 锁定并不是一个好主意.BCL并发集合的全部目的是为性能原因提供无锁并发.代码中的锁定会损害该优势.事实上,我没有看到你需要锁定deq的原因. (14认同)
  • @RichardSchneider如果您需要自己处理并发问题,那么将`ConcurrentQueue <T>`对象替换为更轻量级的`Queue <T>`对象是个好主意. (9认同)
  • 不要定义自己的队列,只需使用继承的队列.如果您这样做,您实际上可以对队列值执行任何其他操作,所有其他功能,但您的新`Enqueue`仍将调用原始队列.换句话说,虽然这个答案被标记为已被接受,但它完全被彻底打破了. (5认同)
  • `q`是对象的私有,因此`lock`将阻止其他线程同时访问. (4认同)
  • @KFL,需要锁定,因为`Count`和`TryDequeue`是两个独立的操作,不在乎BCL Concurrent的同步。 (2认同)

Dav*_*nce 99

我会稍微改变一下......扩展ConcurrentQueue以便能够在FixedSizeQueue上使用Linq扩展

public class FixedSizedQueue<T> : ConcurrentQueue<T>
{
    private readonly object syncObject = new object();

    public int Size { get; private set; }

    public FixedSizedQueue(int size)
    {
        Size = size;
    }

    public new void Enqueue(T obj)
    {
        base.Enqueue(obj);
        lock (syncObject)
        {
            while (base.Count > Size)
            {
                T outObj;
                base.TryDequeue(out outObj);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我的观点是代替子类,也许你应该只是包裹队列...这在所有情况下强制执行所需的行为.此外,由于它是一个自定义存储类,让我们完全自定义,只暴露我们需要的操作,子类化是这里错误的工具恕我直言. (8认同)
  • @mhand如果'某人'想要这样做; 然后他们会选择使用ConcurrentQueue <T>对象开始......这是一个自定义存储类.没有人希望将此提交到.NET框架.你已经试图为它创造一个问题. (4认同)
  • @mhand是的,我得到你所说的..我可以包装一个队列并公开队列的枚举器,以便利用Linq扩展. (3认同)
  • 我同意@mhand 你不应该继承 ConcurrentQueue 因为 Enqueue 方法不是虚拟的。如果需要,您应该代理队列并实现整个接口。 (3认同)
  • 当有人静态地知道该实例为 ConcurrentQueue&lt;T&gt; 时会发生什么,他们只是绕过了您的“new”关键字。 (2认同)

Tod*_*son 28

对于任何发现它有用的人,这里有一些基于Richard Schneider的答案的工作代码:

public class FixedSizedQueue<T>
{
    readonly ConcurrentQueue<T> queue = new ConcurrentQueue<T>();

    public int Size { get; private set; }

    public FixedSizedQueue(int size)
    {
        Size = size;
    }

    public void Enqueue(T obj)
    {
        queue.Enqueue(obj);

        while (queue.Count > Size)
        {
            T outObj;
            queue.TryDequeue(out outObj);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 除了没有实现任何必要的接口以使其成为真正的集合之外,还出于提到的原因(使用 ConcurrentQueue 时锁定是不好的)投票。 (2认同)

Jul*_*iet 11

对于它的价值,这里是一个轻量级的循环缓冲区,其中一些方法标记为安全和不安全使用.

public class CircularBuffer<T> : IEnumerable<T>
{
    readonly int size;
    readonly object locker;

    int count;
    int head;
    int rear;
    T[] values;

    public CircularBuffer(int max)
    {
        this.size = max;
        locker = new object();
        count = 0;
        head = 0;
        rear = 0;
        values = new T[size];
    }

    static int Incr(int index, int size)
    {
        return (index + 1) % size;
    }

    private void UnsafeEnsureQueueNotEmpty()
    {
        if (count == 0)
            throw new Exception("Empty queue");
    }

    public int Size { get { return size; } }
    public object SyncRoot { get { return locker; } }

    #region Count

    public int Count { get { return UnsafeCount; } }
    public int SafeCount { get { lock (locker) { return UnsafeCount; } } }
    public int UnsafeCount { get { return count; } }

    #endregion

    #region Enqueue

    public void Enqueue(T obj)
    {
        UnsafeEnqueue(obj);
    }

    public void SafeEnqueue(T obj)
    {
        lock (locker) { UnsafeEnqueue(obj); }
    }

    public void UnsafeEnqueue(T obj)
    {
        values[rear] = obj;

        if (Count == Size)
            head = Incr(head, Size);
        rear = Incr(rear, Size);
        count = Math.Min(count + 1, Size);
    }

    #endregion

    #region Dequeue

    public T Dequeue()
    {
        return UnsafeDequeue();
    }

    public T SafeDequeue()
    {
        lock (locker) { return UnsafeDequeue(); }
    }

    public T UnsafeDequeue()
    {
        UnsafeEnsureQueueNotEmpty();

        T res = values[head];
        values[head] = default(T);
        head = Incr(head, Size);
        count--;

        return res;
    }

    #endregion

    #region Peek

    public T Peek()
    {
        return UnsafePeek();
    }

    public T SafePeek()
    {
        lock (locker) { return UnsafePeek(); }
    }

    public T UnsafePeek()
    {
        UnsafeEnsureQueueNotEmpty();

        return values[head];
    }

    #endregion


    #region GetEnumerator

    public IEnumerator<T> GetEnumerator()
    {
        return UnsafeGetEnumerator();
    }

    public IEnumerator<T> SafeGetEnumerator()
    {
        lock (locker)
        {
            List<T> res = new List<T>(count);
            var enumerator = UnsafeGetEnumerator();
            while (enumerator.MoveNext())
                res.Add(enumerator.Current);
            return res.GetEnumerator();
        }
    }

    public IEnumerator<T> UnsafeGetEnumerator()
    {
        int index = head;
        for (int i = 0; i < count; i++)
        {
            yield return values[index];
            index = Incr(index, size);
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

我喜欢使用这个Foo()/SafeFoo()/UnsafeFoo()惯例:

  • Foo方法调用UnsafeFoo默认值.
  • UnsafeFoo 方法在没有锁的情况下自由地修改状态,它们应该只调用其他不安全的方法.
  • SafeFoo方法调用UnsafeFoo锁内的方法.

它有点冗长,但它会产生明显的错误,比如在一个本应是线程安全的方法中调用锁外的不安全方法,更明显.


5ar*_*gon 5

我的版本只是普通版本的一个子类Queue..没什么特别的,但看到每个人都参与其中,它仍然与主题标题一致,我不妨把它放在这里。它还返回出列的以防万一。

public sealed class SizedQueue<T> : Queue<T>
{
    public int FixedCapacity { get; }
    public SizedQueue(int fixedCapacity)
    {
        this.FixedCapacity = fixedCapacity;
    }

    /// <summary>
    /// If the total number of item exceed the capacity, the oldest ones automatically dequeues.
    /// </summary>
    /// <returns>The dequeued value, if any.</returns>
    public new T Enqueue(T item)
    {
        base.Enqueue(item);
        if (base.Count > FixedCapacity)
        {
            return base.Dequeue();
        }
        return default;
    }
}
Run Code Online (Sandbox Code Playgroud)


Ali*_*hid 5

这是我对固定大小队列的看法

它使用常规队列,以避免在Count上使用该属性时的同步开销ConcurrentQueue。它还实现了IReadOnlyCollection可以使用 LINQ 方法。其余的与此处的其他答案非常相似。

[Serializable]
[DebuggerDisplay("Count = {" + nameof(Count) + "}, Limit = {" + nameof(Limit) + "}")]
public class FixedSizedQueue<T> : IReadOnlyCollection<T>
{
    private readonly Queue<T> _queue = new Queue<T>();
    private readonly object _lock = new object();

    public int Count { get { lock (_lock) { return _queue.Count; } } }
    public int Limit { get; }

    public FixedSizedQueue(int limit)
    {
        if (limit < 1)
            throw new ArgumentOutOfRangeException(nameof(limit));

        Limit = limit;
    }

    public FixedSizedQueue(IEnumerable<T> collection)
    {
        if (collection is null || !collection.Any())
           throw new ArgumentException("Can not initialize the Queue with a null or empty collection", nameof(collection));

        _queue = new Queue<T>(collection);
        Limit = _queue.Count;
    }

    public void Enqueue(T obj)
    {
        lock (_lock)
        {
            _queue.Enqueue(obj);

            while (_queue.Count > Limit)
                _queue.Dequeue();
        }
    }

    public void Clear()
    {
        lock (_lock)
            _queue.Clear();
    }

    public IEnumerator<T> GetEnumerator()
    {
        lock (_lock)
            return new List<T>(_queue).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
Run Code Online (Sandbox Code Playgroud)


Bra*_*don 5

只是因为还没有人说过......你可以使用 aLinkedList<T>并添加线程安全性:

public class Buffer<T> : LinkedList<T>
{
    private int capacity;

    public Buffer(int capacity)
    {
        this.capacity = capacity;   
    }

    public void Enqueue(T item)
    {
        // todo: add synchronization mechanism
        if (Count == capacity) RemoveLast();
        AddFirst(item);
    }

    public T Dequeue()
    {
        // todo: add synchronization mechanism
        var last = Last.Value;
        RemoveLast();
        return last;
    }
}
Run Code Online (Sandbox Code Playgroud)

需要注意的一件事是,在此示例中,默认枚举顺序为 LIFO。但如果有必要的话,这可以被覆盖。