Fila de tamanho fixo que desenfila automaticamente os valores antigos em novos enques

121

Estou usando ConcurrentQueuepara uma estrutura de dados compartilhada cujo propósito é manter os últimos N objetos passados ​​para ela (tipo de histórico).

Suponha que temos um navegador e queremos os últimos 100 URLs navegados. Eu quero uma fila que descarta (desenfileira) automaticamente a entrada mais antiga (primeira) na inserção de uma nova entrada (enfileirar) quando a capacidade ficar cheia (100 endereços no histórico).

Como posso fazer isso usando System.Collections?

Xaqron
fonte
Não foi feito especificamente para você, mas para qualquer pessoa que se deparar com essa pergunta e possa achá-la útil. aliás, ele fala sobre C # também. Você conseguiu ler todas as respostas (em 2 minutos) e descobrir que não há nenhum código C # aí? De qualquer forma, não tenho certeza, portanto, é um comentário ...
Você pode simplesmente envolver os métodos em um cadeado. Visto que eles são rápidos, você pode bloquear todo o array. Este é provavelmente um tolo embora. Pesquisar implementações de buffer circular com código C # pode encontrar algo para você. De qualquer forma, boa sorte.

Respostas:

111

Eu escreveria uma classe de wrapper que, no Enqueue, verificaria a contagem e, em seguida, removeria da fila quando a contagem ultrapassar o limite.

 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)) ;
        }
     }
 }
Richard Schneider
fonte
4
qé privado para o objeto, de modo que lockirá impedir que outros threads acessem simultaneamente.
Richard Schneider
14
Não é uma boa ideia bloquear. Todo o propósito das coleções simultâneas BCL é fornecer concorrência sem bloqueio por motivos de desempenho. O bloqueio em seu código compromete esse benefício. Na verdade, não vejo um motivo para você precisar bloquear o deq.
KFL 02 de
2
@KFL, precisa ser bloqueado porque Counte TryDequeuesão duas operações independentes que não são sincronizadas pelo BCL Simultâneo.
Richard Schneider
9
@RichardSchneider Se você mesmo precisa lidar com problemas de simultaneidade, seria uma boa ideia trocar o ConcurrentQueue<T>objeto por um Queue<T>objeto mais leve.
0b101010
6
Não defina sua própria fila, apenas use a herdada. Se você fizer o que faz, não poderá fazer mais nada com os valores da fila, todas as outras funções, exceto a nova Enqueue, ainda chamarão a fila original. Em outras palavras, embora essa resposta seja marcada como aceita, ela está completa e totalmente inválida.
Gábor
104

Eu escolheria uma pequena variante ... estender ConcurrentQueue para poder usar extensões Linq em 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);
            }
        }
    }
}
Dave Lawrence
fonte
1
o que acontece quando alguém conhece estaticamente a instância como um ConcurrentQueue <T>, acabou de contornar sua 'nova' palavra-chave.
março
6
@mhand Se 'alguém' quisesse fazer isso; então, eles teriam escolhido usar um objeto ConcurrentQueue <T> para começar ... Esta é uma classe de armazenamento customizada. Ninguém está querendo que isso seja submetido ao .NET framework. Você procurou criar um problema só por criar.
Dave Lawrence
9
meu ponto é, em vez de subclassificar, talvez você deva apenas quebrar a fila ... isso reforça o comportamento desejado em todos os casos. Além disso, uma vez que é uma classe de armazenamento personalizada, vamos torná-la totalmente personalizada, apenas expondo as operações de que precisamos, subclassificação é a ferramenta errada aqui IMHO.
março
3
@mhand Sim, entendi o que você está dizendo ... Eu poderia quebrar uma fila e expor o enumerador da fila para fazer uso de extensões Linq.
Dave Lawrence
1
concordo com @mhand que você não deve herdar ConcurrentQueue porque o método Enqueue não é virtual. Você deve fazer proxy da fila e implementar toda a interface, se desejar.
Chris Marisic
29

Para quem o achar útil, aqui está um código de trabalho baseado na resposta de Richard Schneider acima:

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);
        }
    }
}
Tod Thomson
fonte
1
Votação pelas razões mencionadas (bloquear ao usar um ConcurrentQueue é ruim), além de não implementar nenhuma das interfaces necessárias para que esta seja uma coleção verdadeira.
Josh
11

Para saber o que vale, aqui está um buffer circular leve com alguns métodos marcados para uso seguro e não seguro.

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
}

Gosto de usar a Foo()/SafeFoo()/UnsafeFoo()convenção:

  • Foométodos chamam UnsafeFoocomo padrão.
  • UnsafeFoo métodos modificam o estado livremente sem um bloqueio, eles devem apenas chamar outros métodos não seguros.
  • SafeFoométodos chamam UnsafeFoométodos dentro de um bloqueio.

É um pouco prolixo, mas comete erros óbvios, como chamar métodos inseguros fora de um bloqueio em um método que deveria ser thread-safe, mais aparentes.

Julieta
fonte
5

Aqui está minha opinião sobre a fila de tamanho fixo

Ele usa a fila regular, para evitar a sobrecarga de sincronização quando a Countpropriedade é usada ConcurrentQueue. Ele também implementa IReadOnlyCollectionpara que os métodos LINQ possam ser usados. O resto é muito semelhante às outras respostas aqui.

[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();
    }
}
Ali Zahid
fonte
3

Apenas por diversão, aqui está outra implementação que, acredito, aborda a maioria das preocupações dos comentadores. Em particular, thread-safety é alcançada sem bloqueio e a implementação é oculta pela classe de empacotamento.

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();
  }
}
Erdomke
fonte
1
Isso é quebrado se usado simultaneamente - e se um thread for interrompido após a chamada, _queue.Enqueue(obj)mas antes Interlocked.Increment(ref _count), e as outras chamadas de thread .Count? Seria uma contagem errada. Não verifiquei os outros problemas.
KFL
3

Minha versão é apenas uma subclasse das normais Queue.. nada de especial, mas vendo todos participando e ainda acompanha o título do tópico, posso muito bem colocá-lo aqui. Ele também retorna os desenfileirados apenas no caso.

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;
    }
}
5argon
fonte
2

Vamos adicionar mais uma resposta. Por que isso em vez de outros?

1) Simplicidade. Tentar garantir que o tamanho é bom e bom, mas leva a uma complexidade desnecessária que pode apresentar seus próprios problemas.

2) Implementa IReadOnlyCollection, o que significa que você pode usar o Linq nele e passá-lo para uma variedade de coisas que esperam o IEnumerable.

3) Sem bloqueio. Muitas das soluções acima usam bloqueios, o que é incorreto em uma coleção sem bloqueio.

4) Implementa o mesmo conjunto de métodos, propriedades e interfaces que ConcurrentQueue faz, incluindo IProducerConsumerCollection, que é importante se você deseja usar a coleção com BlockingCollection.

Essa implementação pode potencialmente terminar com mais entradas do que o esperado se TryDequeue falhar, mas a frequência dessa ocorrência não parece valer um código especializado que inevitavelmente prejudicará o desempenho e causará seus próprios problemas inesperados.

Se você realmente deseja garantir um tamanho, implementar um Prune () ou método semelhante parece ser a melhor ideia. Você poderia usar um bloqueio de leitura ReaderWriterLockSlim nos outros métodos (incluindo TryDequeue) e obter um bloqueio de gravação apenas durante a remoção.

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();
}
Josh
fonte
2

Só porque ninguém disse isso ainda ... você pode usar um LinkedList<T>e adicionar o thread de segurança:

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

Uma coisa a ser observada é que a ordem de enumeração padrão será LIFO neste exemplo. Mas isso pode ser substituído, se necessário.

Brandon
fonte
1

Para seu prazer de codificação, apresento a você o ' 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();
       }
   }
}

Exemplo de uso:

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);
}
Chris Hayes
fonte
1
Eu gosto desta implementação, mas observe que quando nenhuma foi adicionada, ela retorna o padrão (T)
Daniel Leach
Se você usar o bloqueio dessa maneira, deverá usar ReaderWriterLockSlim para priorizar seus leitores.
Josh
1

Bem, depende do uso que eu percebi que algumas das soluções acima podem exceder o tamanho quando usadas em um ambiente multip-threaded. De qualquer forma, meu caso de uso era exibir os últimos 5 eventos e há vários threads gravando eventos na fila e um outro thread lendo a partir dele e exibindo-os em um controle Winform. Então essa foi a minha solução.

EDITAR: Como já usamos o bloqueio em nossa implementação, não precisamos realmente do ConcurrentQueue, ele pode melhorar o desempenho.

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

EDIT: Nós realmente não precisamos syncObjectno exemplo acima e podemos preferir usar o queueobjeto, uma vez que não estamos reinicializando queueem nenhuma função e está marcado como de readonlyqualquer maneira.

Mubashar
fonte
0

A resposta aceita terá efeitos colaterais evitáveis.

Mecanismos de bloqueio refinado e livre de bloqueio

Os links abaixo são referências que usei quando escrevi meu exemplo abaixo.

Embora a documentação da Microsoft seja um pouco enganosa, pois eles usam um bloqueio, eles bloqueiam as classes de segmento. As próprias classes de segmento usam 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;
        }
    }
}
jjhayter
fonte
0

Aqui está outra implementação que usa o ConcurrentQueue subjacente tanto quanto possível, enquanto fornece as mesmas interfaces disponibilizadas por meio do 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);

}
Tod Cunningham
fonte
-1

Esta é minha versão da fila:

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

Acho útil ter um construtor baseado em um IEnumerable e acho útil ter um GetSnapshot para ter uma lista segura multithread (array, neste caso) dos itens no momento da chamada, que não sobe erros se a coleção subjacente for alterada.

A verificação de contagem dupla serve para evitar o bloqueio em algumas circunstâncias.

Não é importante
fonte
1
Votando para baixo para bloquear na fila. Se você realmente deseja bloquear, um ReaderWriterLockSlim seria melhor (assumindo que você espera ter um bloqueio de leitura com mais freqüência do que um bloqueio de gravação). GetSnapshot também não é necessário. Se você implementar IReadOnlyCollection <T> (o que você deve fazer para a semântica IEnumerable), ToList () terá a mesma função.
Josh
O ConcurrentQueue lida com os bloqueios em sua implementação, veja os links em minha resposta.
jjhayter