Hàng đợi kích thước cố định sẽ tự động xếp hàng các giá trị cũ trên các giá trị mới


121

Tôi đang sử dụng ConcurrentQueuecho một cấu trúc dữ liệu được chia sẻ với mục đích lưu giữ N đối tượng cuối cùng được chuyển cho nó (loại lịch sử).

Giả sử chúng ta có một trình duyệt và chúng ta muốn có 100 Url được duyệt cuối cùng. Tôi muốn một hàng đợi tự động bỏ (dequeue) mục nhập cũ nhất (đầu tiên) khi chèn mục nhập mới (xếp hàng) khi dung lượng đầy (100 địa chỉ trong lịch sử).

Tôi có thể thực hiện điều đó bằng cách System.Collectionsnào?



Nó không dành riêng cho bạn, nhưng dành cho bất kỳ ai gặp câu hỏi này và có thể thấy nó hữu ích. btw, nó cũng nói về C #. Bạn đã quản lý để đọc tất cả các câu trả lời (trong 2 phút) và phát hiện ra rằng không có mã C # nào ở đó? Dù sao, tôi cũng không chắc bản thân mình, và do đó đó là một nhận xét ...

Bạn chỉ có thể gói các phương pháp trong một khóa. Cho rằng chúng nhanh, bạn chỉ có thể khóa toàn bộ mảng. Đây có lẽ là một bản dupe. Tìm kiếm các triển khai bộ đệm vòng tròn với mã C # có thể tìm thấy cho bạn điều gì đó. Dù sao chúc may mắn.

Câu trả lời:


111

Tôi sẽ viết một lớp wrapper mà trên Enqueue sẽ kiểm tra Count và sau đó Dequeue khi số lượng vượt quá giới hạn.

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

4
qlà riêng tư đối với đối tượng, do đó locksẽ ngăn các luồng khác truy cập đồng thời.
Richard Schneider

14
Không phải là một ý kiến ​​hay để khóa. Toàn bộ mục đích của bộ sưu tập đồng thời BCL là cung cấp đồng thời khóa miễn phí vì lý do hiệu suất. Việc khóa mã của bạn sẽ ảnh hưởng đến lợi ích đó. Trên thực tế, tôi không thấy lý do gì bạn cần khóa deq.
KFL

2
@KFL, cần phải khóa vì CountTryDequeuelà hai hoạt động độc lập không được đồng bộ bởi BCL Concurrent.
Richard Schneider

9
@RichardSchneider Nếu bạn cần tự xử lý các vấn đề về đồng thời thì tốt hơn là bạn nên hoán đổi ConcurrentQueue<T>đối tượng cho một Queue<T>đối tượng nhẹ hơn.
0b101010

6
Không xác định hàng đợi của riêng bạn, chỉ sử dụng hàng kế thừa. Nếu bạn làm như bạn làm, bạn thực sự không thể làm gì khác với các giá trị hàng đợi, tất cả các hàm khác nhưng chức năng mới của bạn Enqueuesẽ vẫn gọi hàng đợi ban đầu. Nói cách khác, mặc dù câu trả lời này được đánh dấu là được chấp nhận, nhưng nó hoàn toàn bị hỏng.
Gábor

104

Tôi muốn có một biến thể nhỏ ... mở rộng ConcurrentQueue để có thể sử dụng các phần mở rộng Linq trên FixedSizeQueue

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

1
điều gì sẽ xảy ra khi ai đó tĩnh biết trường hợp này là ConcurrentQueue <T>, họ vừa phá vỡ từ khóa 'mới' của bạn.
mhand

6
@mhand Nếu 'ai đó' muốn làm điều đó; thì họ sẽ chọn sử dụng đối tượng ConcurrentQueue <T> để bắt đầu ... Đây là một lớp lưu trữ tùy chỉnh. Không ai đang tìm cách gửi điều này vào .NET framework. Bạn đã tìm cách tạo ra một vấn đề vì lợi ích của nó.
Dave Lawrence

9
quan điểm của tôi là thay vì phân lớp có thể bạn chỉ nên quấn hàng đợi ... điều này thực thi hành vi mong muốn trong mọi trường hợp. Ngoài ra, vì nó là một lớp lưu trữ tùy chỉnh, hãy làm cho nó hoàn toàn tùy chỉnh, chỉ hiển thị các hoạt động chúng ta cần, phân lớp là công cụ sai ở đây IMHO.
mhand

3
@mhand Vâng, tôi hiểu bạn đang nói gì .. Tôi có thể quấn một hàng đợi và hiển thị điều tra viên của hàng đợi để sử dụng các tiện ích mở rộng Linq.
Dave Lawrence

1
tôi đồng ý với @mhand rằng bạn không nên kế thừa ConcurrentQueue vì phương thức Enqueue không phải là ảo. Bạn nên ủy quyền hàng đợi và triển khai toàn bộ giao diện nếu muốn.
Chris Marisic

29

Đối với bất kỳ ai thấy nó hữu ích, đây là một số mã làm việc dựa trên câu trả lời của Richard Schneider ở trên:

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

1
Bỏ phiếu vì các lý do đã đề cập (khóa khi sử dụng ConcurrentQueue là không tốt) ngoài việc không triển khai bất kỳ giao diện cần thiết nào để đây là một tập hợp thực sự.
Josh

11

Đối với giá trị của nó, đây là một bộ đệm hình tròn nhẹ với một số phương pháp được đánh dấu để sử dụng an toàn và không an toàn.

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
}

Tôi thích sử dụng Foo()/SafeFoo()/UnsafeFoo()quy ước:

  • Foophương thức gọi UnsafeFoonhư một mặc định.
  • UnsafeFoo các phương thức sửa đổi trạng thái tự do mà không có khóa, chúng chỉ nên gọi các phương thức không an toàn khác.
  • SafeFoocác phương thức gọi UnsafeFoocác phương thức bên trong một khóa.

Nó hơi dài dòng, nhưng nó tạo ra các lỗi rõ ràng, như gọi các phương thức không an toàn bên ngoài một khóa trong một phương thức được cho là an toàn theo luồng, rõ ràng hơn.


5

Đây là quyết định của tôi về Hàng đợi kích thước cố định

Nó sử dụng Hàng đợi thông thường, để tránh chi phí đồng bộ hóa khi thuộc Counttính được sử dụng trên ConcurrentQueue. Nó cũng thực hiện IReadOnlyCollectionđể các phương pháp LINQ có thể được sử dụng. Phần còn lại rất giống với các câu trả lời khác ở đây.

[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();
    }
}

3

Vui thôi, đây là một cách triển khai khác mà tôi tin rằng giải quyết hầu hết các mối quan tâm của người bình luận. Đặc biệt, sự an toàn của luồng đạt được mà không cần khóa và việc triển khai bị ẩn bởi lớp bao bọc.

public class FixedSizeQueue<T> : IReadOnlyCollection<T>
{
  private ConcurrentQueue<T> _queue = new ConcurrentQueue<T>();
  private int _count;

  public int Limit { get; private set; }

  public FixedSizeQueue(int limit)
  {
    this.Limit = limit;
  }

  public void Enqueue(T obj)
  {
    _queue.Enqueue(obj);
    Interlocked.Increment(ref _count);

    // Calculate the number of items to be removed by this thread in a thread safe manner
    int currentCount;
    int finalCount;
    do
    {
      currentCount = _count;
      finalCount = Math.Min(currentCount, this.Limit);
    } while (currentCount != 
      Interlocked.CompareExchange(ref _count, finalCount, currentCount));

    T overflow;
    while (currentCount > finalCount && _queue.TryDequeue(out overflow))
      currentCount--;
  }

  public int Count
  {
    get { return _count; }
  }

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

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

1
Điều này bị hỏng nếu được sử dụng đồng thời - điều gì sẽ xảy ra nếu một luồng được gọi _queue.Enqueue(obj)trước sau khi gọi nhưng trước đó Interlocked.Increment(ref _count)và luồng khác gọi .Count? Nó sẽ bị đếm sai. Tôi chưa kiểm tra các vấn đề khác.
KFL

3

Phiên bản của tôi chỉ là một lớp con của Queuenhững cái bình thường .. không có gì đặc biệt nhưng thấy mọi người tham gia và nó vẫn đi với tiêu đề chủ đề tôi cũng có thể đặt nó ở đây. Nó cũng trả về những cái bị mất giá trị trong trường hợp.

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

2

Hãy thêm một câu trả lời nữa. Tại sao điều này hơn những người khác?

1) Tính đơn giản. Cố gắng đảm bảo kích thước là tốt và tốt nhưng dẫn đến sự phức tạp không cần thiết có thể bộc lộ các vấn đề riêng của nó.

2) Triển khai IReadOnlyCollection, nghĩa là bạn có thể sử dụng Linq trên đó và chuyển nó vào nhiều thứ khác nhau mà IEnumerable mong đợi.

3) Không khóa. Nhiều giải pháp ở trên sử dụng khóa, điều này không chính xác đối với bộ sưu tập không khóa.

4) Triển khai cùng một tập hợp các phương thức, thuộc tính và giao diện mà ConcurrentQueue thực hiện, bao gồm cả IProductionerConsumerCollection, điều này rất quan trọng nếu bạn muốn sử dụng bộ sưu tập với BlockingCollection.

Việc triển khai này có khả năng kết thúc với nhiều mục nhập hơn dự kiến ​​nếu TryDequeue không thành công, nhưng tần suất xuất hiện của điều đó dường như không đáng giá đối với mã chuyên dụng, chắc chắn sẽ cản trở hiệu suất và gây ra các vấn đề không mong muốn của chính nó.

Nếu bạn hoàn toàn muốn đảm bảo kích thước, việc triển khai Prune () hoặc phương thức tương tự có vẻ là ý tưởng tốt nhất. Bạn có thể sử dụng khóa đọc ReaderWriterLockSlim trong các phương pháp khác (bao gồm cả TryDequeue) và chỉ thực hiện khóa ghi khi lược bỏ.

class ConcurrentFixedSizeQueue<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>, ICollection {
    readonly ConcurrentQueue<T> m_concurrentQueue;
    readonly int m_maxSize;

    public int Count => m_concurrentQueue.Count;
    public bool IsEmpty => m_concurrentQueue.IsEmpty;

    public ConcurrentFixedSizeQueue (int maxSize) : this(Array.Empty<T>(), maxSize) { }

    public ConcurrentFixedSizeQueue (IEnumerable<T> initialCollection, int maxSize) {
        if (initialCollection == null) {
            throw new ArgumentNullException(nameof(initialCollection));
        }

        m_concurrentQueue = new ConcurrentQueue<T>(initialCollection);
        m_maxSize = maxSize;
    }

    public void Enqueue (T item) {
        m_concurrentQueue.Enqueue(item);

        if (m_concurrentQueue.Count > m_maxSize) {
            T result;
            m_concurrentQueue.TryDequeue(out result);
        }
    }

    public void TryPeek (out T result) => m_concurrentQueue.TryPeek(out result);
    public bool TryDequeue (out T result) => m_concurrentQueue.TryDequeue(out result);

    public void CopyTo (T[] array, int index) => m_concurrentQueue.CopyTo(array, index);
    public T[] ToArray () => m_concurrentQueue.ToArray();

    public IEnumerator<T> GetEnumerator () => m_concurrentQueue.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator () => GetEnumerator();

    // Explicit ICollection implementations.
    void ICollection.CopyTo (Array array, int index) => ((ICollection)m_concurrentQueue).CopyTo(array, index);
    object ICollection.SyncRoot => ((ICollection) m_concurrentQueue).SyncRoot;
    bool ICollection.IsSynchronized => ((ICollection) m_concurrentQueue).IsSynchronized;

    // Explicit IProducerConsumerCollection<T> implementations.
    bool IProducerConsumerCollection<T>.TryAdd (T item) => ((IProducerConsumerCollection<T>) m_concurrentQueue).TryAdd(item);
    bool IProducerConsumerCollection<T>.TryTake (out T item) => ((IProducerConsumerCollection<T>) m_concurrentQueue).TryTake(out item);

    public override int GetHashCode () => m_concurrentQueue.GetHashCode();
    public override bool Equals (object obj) => m_concurrentQueue.Equals(obj);
    public override string ToString () => m_concurrentQueue.ToString();
}

2

Chỉ bởi vì chưa có ai nói điều đó .. bạn có thể sử dụng a LinkedList<T>và thêm sự an toàn của chuỗi:

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

Một điều cần lưu ý là thứ tự liệt kê mặc định sẽ là LIFO trong ví dụ này. Nhưng điều đó có thể được ghi đè nếu cần thiết.


1

Vì niềm vui viết mã của bạn, tôi xin gửi đến bạn ' ConcurrentDeck'

public class ConcurrentDeck<T>
{
   private readonly int _size;
   private readonly T[] _buffer;
   private int _position = 0;

   public ConcurrentDeck(int size)
   {
       _size = size;
       _buffer = new T[size];
   }

   public void Push(T item)
   {
       lock (this)
       {
           _buffer[_position] = item;
           _position++;
           if (_position == _size) _position = 0;
       }
   }

   public T[] ReadDeck()
   {
       lock (this)
       {
           return _buffer.Skip(_position).Union(_buffer.Take(_position)).ToArray();
       }
   }
}

Cách sử dụng ví dụ:

void Main()
{
    var deck = new ConcurrentDeck<Tuple<string,DateTime>>(25);
    var handle = new ManualResetEventSlim();
    var task1 = Task.Factory.StartNew(()=>{
    var timer = new System.Timers.Timer();
    timer.Elapsed += (s,a) => {deck.Push(new Tuple<string,DateTime>("task1",DateTime.Now));};
    timer.Interval = System.TimeSpan.FromSeconds(1).TotalMilliseconds;
    timer.Enabled = true;
    handle.Wait();
    }); 
    var task2 = Task.Factory.StartNew(()=>{
    var timer = new System.Timers.Timer();
    timer.Elapsed += (s,a) => {deck.Push(new Tuple<string,DateTime>("task2",DateTime.Now));};
    timer.Interval = System.TimeSpan.FromSeconds(.5).TotalMilliseconds;
    timer.Enabled = true;
    handle.Wait();
    }); 
    var task3 = Task.Factory.StartNew(()=>{
    var timer = new System.Timers.Timer();
    timer.Elapsed += (s,a) => {deck.Push(new Tuple<string,DateTime>("task3",DateTime.Now));};
    timer.Interval = System.TimeSpan.FromSeconds(.25).TotalMilliseconds;
    timer.Enabled = true;
    handle.Wait();
    }); 
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
    handle.Set();
    var outputtime = DateTime.Now;
    deck.ReadDeck().Select(d => new {Message = d.Item1, MilliDiff = (outputtime - d.Item2).TotalMilliseconds}).Dump(true);
}

1
Tôi như thực hiện điều này, nhưng làm lưu ý rằng khi không đã được thêm vào nó sẽ trả về mặc định (T)
Daniel Leach

Nếu bạn sử dụng khóa theo cách này, bạn nên sử dụng ReaderWriterLockSlim để ưu tiên người đọc của mình.
Josh

1

Nó phụ thuộc vào việc sử dụng, tôi đã nhận thấy rằng một số giải pháp trên có thể vượt quá kích thước khi được sử dụng trong môi trường đa luồng. Dù sao thì trường hợp sử dụng của tôi là hiển thị 5 sự kiện cuối cùng và có nhiều luồng ghi sự kiện vào hàng đợi và một luồng khác đọc từ nó và hiển thị nó trong Winform Control. Vì vậy, đây là giải pháp của tôi.

CHỈNH SỬA: Vì chúng tôi đã sử dụng khóa trong quá trình triển khai của mình nên chúng tôi không thực sự cần ConcurrentQueue, nó có thể cải thiện hiệu suất.

class FixedSizedConcurrentQueue<T> 
{
    readonly Queue<T> queue = new Queue<T>();
    readonly object syncObject = new object();

    public int MaxSize { get; private set; }

    public FixedSizedConcurrentQueue(int maxSize)
    {
        MaxSize = maxSize;
    }

    public void Enqueue(T obj)
    {
        lock (syncObject)
        {
            queue.Enqueue(obj);
            while (queue.Count > MaxSize)
            {
                queue.Dequeue();
            }
        }
    }

    public T[] ToArray()
    {
        T[] result = null;
        lock (syncObject)
        {
            result = queue.ToArray();
        }

        return result;
    }

    public void Clear()
    {
        lock (syncObject)
        {
            queue.Clear();
        }
    }
}

CHỈNH SỬA: Chúng tôi không thực sự cần syncObjecttrong ví dụ trên và chúng tôi có thể sử dụng queueđối tượng thay vì chúng tôi không khởi tạo lại queuetrong bất kỳ hàm nào và nó được đánh dấu là readonly.


0

Câu trả lời được chấp nhận là sẽ có những tác dụng phụ có thể tránh được.

Cơ chế khóa và không khóa hạt mịn

Các liên kết bên dưới là các tham chiếu mà tôi đã sử dụng khi viết ví dụ của mình bên dưới.

Trong khi tài liệu từ Microsoft có một chút sai lầm vì họ sử dụng một khóa nhưng họ vẫn khóa các lớp phân tách. Bản thân các lớp phân đoạn sử dụng Interlocked.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Lib.Core
{
    // Sources: 
    // https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/
    // https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked?view=netcore-3.1
    // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs
    // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueueSegment.cs

    /// <summary>
    /// Concurrent safe circular buffer that will used a fixed capacity specified and resuse slots as it goes.
    /// </summary>
    /// <typeparam name="TObject">The object that you want to go into the slots.</typeparam>
    public class ConcurrentCircularBuffer<TObject>
    {
        private readonly ConcurrentQueue<TObject> _queue;

        public int Capacity { get; private set; }

        public ConcurrentCircularBuffer(int capacity)
        {
            if(capacity <= 0)
            {
                throw new ArgumentException($"The capacity specified '{capacity}' is not valid.", nameof(capacity));
            }

            // Setup the queue to the initial capacity using List's underlying implementation.
            _queue = new ConcurrentQueue<TObject>(new List<TObject>(capacity));

            Capacity = capacity;
        }

        public void Enqueue(TObject @object)
        {
            // Enforce the capacity first so the head can be used instead of the entire segment (slow).
            while (_queue.Count + 1 > Capacity)
            {
                if (!_queue.TryDequeue(out _))
                {
                    // Handle error condition however you want to ie throw, return validation object, etc.
                    var ex = new Exception("Concurrent Dequeue operation failed.");
                    ex.Data.Add("EnqueueObject", @object);
                    throw ex;
                }
            }

            // Place the item into the queue
            _queue.Enqueue(@object);
        }

        public TObject Dequeue()
        {
            if(_queue.TryDequeue(out var result))
            {
                return result;
            }

            return default;
        }
    }
}

0

Đây là một triển khai khác sử dụng ConcurrentQueue bên dưới càng nhiều càng tốt trong khi cung cấp các giao diện tương tự được cung cấp qua ConcurrentQueue.

/// <summary>
/// This is a FIFO concurrent queue that will remove the oldest added items when a given limit is reached.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class FixedSizedConcurrentQueue<TValue> : IProducerConsumerCollection<TValue>, IReadOnlyCollection<TValue>
{
    private readonly ConcurrentQueue<TValue> _queue;

    private readonly object _syncObject = new object();

    public int LimitSize { get; }

    public FixedSizedConcurrentQueue(int limit)
    {
        _queue = new ConcurrentQueue<TValue>();
        LimitSize = limit;
    }

    public FixedSizedConcurrentQueue(int limit, System.Collections.Generic.IEnumerable<TValue> collection)
    {
        _queue = new ConcurrentQueue<TValue>(collection);
        LimitSize = limit;

    }

    public int Count => _queue.Count;

    bool ICollection.IsSynchronized => ((ICollection) _queue).IsSynchronized;

    object ICollection.SyncRoot => ((ICollection)_queue).SyncRoot; 

    public bool IsEmpty => _queue.IsEmpty;

    // Not supported until .NET Standard 2.1
    //public void Clear() => _queue.Clear();

    public void CopyTo(TValue[] array, int index) => _queue.CopyTo(array, index);

    void ICollection.CopyTo(Array array, int index) => ((ICollection)_queue).CopyTo(array, index);

    public void Enqueue(TValue obj)
    {
        _queue.Enqueue(obj);
        lock( _syncObject )
        {
            while( _queue.Count > LimitSize ) {
                _queue.TryDequeue(out _);
            }
        }
    }

    public IEnumerator<TValue> GetEnumerator() => _queue.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<TValue>)this).GetEnumerator();

    public TValue[] ToArray() => _queue.ToArray();

    public bool TryAdd(TValue item)
    {
        Enqueue(item);
        return true;
    }

    bool IProducerConsumerCollection<TValue>.TryTake(out TValue item) => TryDequeue(out item);

    public bool TryDequeue(out TValue result) => _queue.TryDequeue(out result);

    public bool TryPeek(out TValue result) => _queue.TryPeek(out result);

}

-1

Đây là phiên bản hàng đợi của tôi:

public class FixedSizedQueue<T> {
  private object LOCK = new object();
  ConcurrentQueue<T> queue;

  public int MaxSize { get; set; }

  public FixedSizedQueue(int maxSize, IEnumerable<T> items = null) {
     this.MaxSize = maxSize;
     if (items == null) {
        queue = new ConcurrentQueue<T>();
     }
     else {
        queue = new ConcurrentQueue<T>(items);
        EnsureLimitConstraint();
     }
  }

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

  private void EnsureLimitConstraint() {
     if (queue.Count > MaxSize) {
        lock (LOCK) {
           T overflow;
           while (queue.Count > MaxSize) {
              queue.TryDequeue(out overflow);
           }
        }
     }
  }


  /// <summary>
  /// returns the current snapshot of the queue
  /// </summary>
  /// <returns></returns>
  public T[] GetSnapshot() {
     return queue.ToArray();
  }
}

Tôi thấy hữu ích khi có một hàm tạo được xây dựng dựa trên IEnumerable và tôi thấy hữu ích khi có GetSnapshot để có danh sách an toàn đa luồng (trong trường hợp này là mảng) của các mục tại thời điểm cuộc gọi, nó không tăng lỗi nếu bộ sưu tập lớp dưới thay đổi.

Kiểm tra Đếm kép là để ngăn chặn khóa trong một số trường hợp.


1
Bỏ phiếu để khóa trên hàng đợi. Nếu bạn thực sự muốn khóa, thì tốt nhất nên dùng ReaderWriterLockSlim (giả sử bạn muốn thực hiện khóa đọc thường xuyên hơn khóa ghi). GetSnapshot cũng không cần thiết. Nếu bạn triển khai IReadOnlyCollection <T> (mà bạn nên sử dụng cho ngữ nghĩa IEnumerable), ToList () sẽ phục vụ cùng một chức năng.
Josh

ConcurrentQueue xử lý các khóa trong quá trình triển khai của nó, hãy xem các liên kết trong câu trả lời của tôi.
jjhayter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.