Coda di dimensioni fisse che rimuove automaticamente dalla coda i vecchi valori su nuovi enques


121

Sto usando ConcurrentQueueper una struttura dati condivisa il cui scopo è mantenere gli ultimi N oggetti passati ad essa (tipo di cronologia).

Supponiamo di avere un browser e di voler avere gli ultimi 100 URL sfogliati. Voglio una coda che elimina automaticamente (rimuove dalla coda) la voce più vecchia (prima) all'inserimento di una nuova voce (accodamento) quando la capacità è piena (100 indirizzi nella cronologia).

Come posso farlo utilizzando System.Collections?



Non era pensato specificamente per te, ma per chiunque incontri questa domanda e potrebbe trovarla utile. a proposito, parla anche di C #. Sei riuscito a leggere tutte le risposte (in 2 minuti) e hai scoperto che non c'è codice C # lì? Ad ogni modo, non ne sono sicuro io stesso, e quindi è un commento ...

Puoi semplicemente racchiudere i metodi in un lucchetto. Dato che sono veloci, puoi semplicemente bloccare l'intero array. Questo è probabilmente un duplicato però. La ricerca di implementazioni di buffer circolare con codice C # potrebbe trovare qualcosa. Comunque, buona fortuna.

Risposte:


111

Vorrei scrivere una classe wrapper che su Enqueue controllerebbe il conteggio e quindi la rimozione dalla coda quando il conteggio supera il 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)) ;
        }
     }
 }

4
qè privato per l'oggetto, in modo che lockimpedirà ad altri thread l'accesso simultaneo.
Richard Schneider

14
Non è una buona idea bloccare. L'intero scopo delle raccolte simultanee BCL è fornire una concorrenza senza blocchi per motivi di prestazioni. Il blocco del codice compromette tale vantaggio. In effetti non vedo un motivo per cui devi bloccare il deq.
KFL

2
@KFL, è necessario bloccare perché Counte TryDequeuesono due operazioni indipendenti che non interessano sincronizzate da BCL Concurrent.
Richard Schneider

9
@RichardSchneider Se hai bisogno di gestire tu stesso i problemi di concorrenza, sarebbe una buona idea scambiare l' ConcurrentQueue<T>oggetto con un Queue<T>oggetto più leggero.
0b101010

6
Non definire la tua coda, usa solo quella ereditata. Se fai come fai, non puoi effettivamente fare nient'altro con i valori della coda, tutte le altre funzioni tranne la tua nuova Enqueuechiameranno comunque la coda originale. In altre parole, sebbene questa risposta sia contrassegnata come accettata, è completamente e completamente rotta.
Gábor

104

Vorrei una leggera variante ... estendere ConcurrentQueue in modo da poter utilizzare le estensioni Linq su 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
cosa succede quando qualcuno conosce staticamente l'istanza come ConcurrentQueue <T>, ha appena aggirato la tua parola chiave "nuova".
mhand

6
@mhand Se "qualcuno" volesse farlo; quindi avrebbero scelto di utilizzare un oggetto ConcurrentQueue <T> per cominciare ... Questa è una classe di archiviazione personalizzata. Nessuno sta cercando di inviare questo al framework .NET. Hai cercato di creare un problema per il gusto di farlo.
Dave Lawrence

9
il mio punto è invece di creare sottoclassi forse dovresti semplicemente avvolgere la coda ... questo impone il comportamento desiderato in tutti i casi. Inoltre, poiché è una classe di archiviazione personalizzata, rendiamola completamente personalizzata, esponiamo solo le operazioni di cui abbiamo bisogno, la sottoclasse è lo strumento sbagliato qui IMHO.
mhand

3
@mhand Sì, ho capito cosa stai dicendo .. Potrei avvolgere una coda ed esporre l'enumeratore della coda in modo da utilizzare le estensioni Linq.
Dave Lawrence

1
Sono d'accordo con @mhand che non dovresti ereditare ConcurrentQueue perché il metodo Enqueue non è virtuale. È necessario eseguire il proxy della coda e implementare l'intera interfaccia, se lo si desidera.
Chris Marisic

29

Per chiunque lo trovi utile, ecco un codice funzionante basato sulla risposta di Richard Schneider sopra:

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
Votare per i motivi menzionati (il blocco quando si utilizza un ConcurrentQueue non è corretto) oltre a non implementare nessuna delle interfacce richieste affinché questa sia una vera raccolta.
Josh

11

Per quel che vale, ecco un buffer circolare leggero con alcuni metodi contrassegnati per un uso sicuro e non sicuro.

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
}

Mi piace usare la Foo()/SafeFoo()/UnsafeFoo()convenzione:

  • Foometodi chiamano UnsafeFoocome impostazione predefinita.
  • UnsafeFoo i metodi modificano lo stato liberamente senza un blocco, dovrebbero chiamare solo altri metodi non sicuri.
  • SafeFooi metodi chiamano UnsafeFoometodi all'interno di un lucchetto.

È un po 'prolisso, ma fa errori evidenti, come chiamare metodi non sicuri al di fuori di un blocco in un metodo che dovrebbe essere thread-safe, più evidente.


5

Ecco la mia opinione sulla coda di dimensioni fisse

Utilizza la coda regolare, per evitare l'overhead di sincronizzazione quando la Countproprietà viene utilizzata ConcurrentQueue. Implementa anche in IReadOnlyCollectionmodo che possano essere usati i metodi LINQ. Il resto è molto simile alle altre risposte qui.

[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

Solo per divertimento, ecco un'altra implementazione che credo risolva la maggior parte delle preoccupazioni dei commentatori. In particolare, la thread safety viene ottenuta senza blocco e l'implementazione è nascosta dalla classe wrapping.

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
Questo non funziona se usato contemporaneamente - cosa succede se un thread viene interrotto dopo la chiamata _queue.Enqueue(obj)ma prima Interlocked.Increment(ref _count)e l'altro thread chiama .Count? Otterrebbe un conteggio sbagliato. Non ho controllato gli altri problemi.
KFL

3

La mia versione è solo una sottoclasse di Queuequelle normali .. niente di speciale ma vedendo tutti partecipare e va ancora con il titolo dell'argomento potrei anche metterlo qui. Restituisce anche quelli rimossi dalla coda per ogni evenienza.

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

Aggiungiamo un'altra risposta. Perché questo rispetto ad altri?

1) Semplicità. Cercare di garantire le dimensioni va bene, ma porta a una complessità non necessaria che può mostrare i suoi problemi.

2) Implementa IReadOnlyCollection, il che significa che puoi usare Linq su di esso e passarlo in una varietà di cose che si aspettano IEnumerable.

3) Nessun blocco. Molte delle soluzioni precedenti utilizzano i blocchi, il che non è corretto in una raccolta senza blocco.

4) Implementa lo stesso insieme di metodi, proprietà e interfacce di ConcurrentQueue, incluso IProducerConsumerCollection, che è importante se si desidera utilizzare la raccolta con BlockingCollection.

Questa implementazione potrebbe potenzialmente finire con più voci del previsto se TryDequeue fallisce, ma la frequenza con cui si verifica non sembra degna di un codice specializzato che inevitabilmente ostacolerà le prestazioni e causerà i suoi problemi imprevisti.

Se vuoi assolutamente garantire una dimensione, l'implementazione di un metodo Prune () o simile sembra l'idea migliore. È possibile utilizzare un blocco di lettura ReaderWriterLockSlim negli altri metodi (incluso TryDequeue) e prendere un blocco di scrittura solo durante l'eliminazione.

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

Solo perché nessuno lo ha ancora detto .. puoi usare a LinkedList<T>e aggiungere la sicurezza del thread:

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

Una cosa da notare è che l'ordine di enumerazione predefinito sarà LIFO in questo esempio. Ma questo può essere ignorato se necessario.


1

Per il tuo piacere di codifica, ti invio il 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();
       }
   }
}

Utilizzo di esempio:

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
Mi piace questa implementazione ma tieni presente che quando non ne è stata aggiunta nessuna restituisce il valore predefinito (T)
Daniel Leach,

Se usi il blocco in questo modo, dovresti usare ReaderWriterLockSlim per dare la priorità ai tuoi lettori.
Josh

1

Beh, dipende dall'uso. Ho notato che alcune delle soluzioni sopra descritte potrebbero superare le dimensioni se utilizzate in ambienti multi-thread. Ad ogni modo il mio caso d'uso è stato quello di visualizzare gli ultimi 5 eventi e ci sono più thread che scrivono eventi nella coda e un altro thread che legge da esso e lo visualizza in un controllo Winform. Quindi questa era la mia soluzione.

EDIT: poiché utilizziamo già il blocco all'interno della nostra implementazione, non abbiamo davvero bisogno di ConcurrentQueue, potrebbe migliorare le prestazioni.

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: non abbiamo davvero bisogno syncObjectnell'esempio precedente e possiamo piuttosto usare queueobject poiché non stiamo reinizializzando queuein nessuna funzione ed è readonlycomunque contrassegnato come .


0

La risposta accettata avrà effetti collaterali evitabili.

Bloccaggio a grana fine e meccanismi senza bloccaggio

I collegamenti sottostanti sono riferimenti che ho usato quando ho scritto il mio esempio di seguito.

Sebbene la documentazione di Microsoft sia un po 'fuorviante in quanto utilizzano un blocco, tuttavia bloccano le classi di segmentazione. Le stesse classi di segmenti utilizzano 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

Ecco un'altra implementazione che utilizza il più possibile ConcurrentQueue sottostante fornendo le stesse interfacce rese disponibili tramite 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

Questa è la mia versione della coda:

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

Trovo utile avere un costruttore costruito su un IEnumerable e trovo utile avere un GetSnapshot per avere una lista sicura multithread (array in questo caso) degli elementi al momento della chiamata, che non sale errori se la raccolta sottostante cambia.

Il doppio controllo del conteggio serve a prevenire il blocco in alcune circostanze.


1
Votazione per blocco in coda. Se vuoi assolutamente bloccare, un ReaderWriterLockSlim sarebbe il migliore (supponendo che ti aspetti di prendere un blocco di lettura più spesso di un blocco di scrittura). Anche GetSnapshot non è necessario. Se si implementa IReadOnlyCollection <T> (che dovresti per la semantica IEnumerable), ToList () servirà la stessa funzione.
Josh

ConcurrentQueue gestisce i blocchi nella sua implementazione, vedere i collegamenti nella mia risposta.
jjhayter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.