Yeni kodlamalar üzerine eski değerleri otomatik olarak çıkaran sabit boyut kuyruğu


121

ConcurrentQueueAmacı, kendisine geçirilen son N nesneyi tutmak olan paylaşılan bir veri yapısı için kullanıyorum (tarih türü).

Bir tarayıcımız olduğunu ve göz atılan son 100 URL'ye sahip olmak istediğimizi varsayalım. Kapasite dolduğunda (geçmişte 100 adres) yeni giriş eklenmesi (sıraya sokma) üzerine en eski (ilk) girişi otomatik olarak bırakan (kuyruğundan çıkaran) bir kuyruk istiyorum.

Bunu kullanarak nasıl başarabilirim System.Collections?



Bu, özellikle sizin için değil, bu soruyla karşılaşan ve onu faydalı bulan herkes içindir. btw, C # hakkında da konuşuyor. Tüm cevapları (2 dakika içinde) okuyup orada C # kodu olmadığını anladınız mı? Her neyse, kendimden emin değilim ve bu yüzden bir yorum ...

Yöntemleri bir kilide sarabilirsiniz. Hızlı oldukları göz önüne alındığında, tüm diziyi kilitleyebilirsiniz. Bu muhtemelen bir dupe. C # kodu ile dairesel tampon uygulamalarını aramak size bir şeyler bulabilir. Her neyse, iyi şanslar.

Yanıtlar:


111

Enqueue'da Count'u kontrol edecek ve sayı sınırı aştığında Kuyruktan Çıkaracak bir sarmalayıcı sınıfı yazardım.

 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
qnesneye özeldir, böylece lockdiğer iş parçacıklarının eşzamanlı erişimini engeller.
Richard Schneider

14
Kilitlemek iyi bir fikir değil. BCL eşzamanlı koleksiyonlarının tüm amacı, performans nedeniyle kilitsiz eşzamanlılık sağlamaktır. Kodunuzdaki kilitlenme bu avantajı tehlikeye atar. Aslında deq'i kilitlemeniz için bir neden göremiyorum.
KFL

2
@KFL, gerek kilitlemek için çünkü Countve TryDequeueBCL eş zamanlı tarafından senkronize olmayan bakım iki bağımsız operasyonlardır.
Richard Schneider

9
@RichardSchneider Eşzamanlılık sorunlarını kendiniz halletmeniz gerekiyorsa, ConcurrentQueue<T>nesneyi Queue<T>daha hafif bir nesne ile değiştirmek iyi bir fikir olacaktır .
0b101010

6
Kendi sıranızı tanımlamayın, sadece devralınanı kullanın. Yaptığınız gibi yaparsanız, kuyruk değerleriyle aslında başka hiçbir şey yapamazsınız, diğer tüm işlevler ancak Enqueueyeniniz yine de orijinal kuyruğu çağırır. Diğer bir deyişle, bu cevap kabul edildi olarak işaretlense de, tamamen ve tamamen bozuktur.
Gábor

104

Ben küçük bir varyantı seçerdim ... ConcurrentQueue'yu genişleterek FixedSizeQueue'da Linq uzantılarını kullanabilir

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
birisi örneği statik olarak ConcurrentQueue <T> olarak bildiğinde, 'yeni' anahtar kelimenizi atlattığında ne olur.
mhand

6
@mhand Eğer 'birisi' bunu yapmak isterse; daha sonra başlamak için ConcurrentQueue <T> nesnesi kullanmayı seçerlerdi ... Bu özel bir depolama sınıfıdır. Kimse bunun .NET çerçevesine gönderilmesini istemiyor. Bunun uğruna bir sorun yaratmaya çalıştınız.
Dave Lawrence

9
Demek istediğim, alt sınıflandırma yapmak yerine, belki de kuyruğu sarmalısınız ... bu, her durumda istenen davranışı zorlar. Ayrıca, özel bir depolama sınıfı olduğu için, tamamen özel yapalım, sadece ihtiyacımız olan işlemleri ortaya çıkaralım, burada alt sınıflandırma yanlış araçtır IMHO.
mhand

3
@mhand Evet, ne dediğini anlıyorum .. Linq uzantılarını kullanmak için bir kuyruğu kapatabilir ve kuyruğun numaralandırıcısını açığa çıkarabilirim.
Dave Lawrence

1
@mhand'e katılıyorum, ConcurrentQueue'yu devralmamalısınız çünkü Enqueue yöntemi sanal değil. Kuyruğu proxy yapmalı ve istenirse tüm arayüzü uygulamalısınız.
Chris Marisic

29

Yararlı bulan herkes için, Richard Schneider'ın yukarıdaki cevabına dayanan bazı çalışma kodları aşağıda verilmiştir:

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
Bunun gerçek bir koleksiyon olması için gerekli arabirimlerin hiçbirini uygulamamanın yanı sıra belirtilen nedenlerle (ConcurrentQueue kullanırken kilitlemek kötüdür) oylama.
Josh

11

Değeri ne olursa olsun, burada güvenli ve güvensiz kullanım için işaretlenmiş bazı yöntemlerle hafif dairesel bir tampon var.

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
}

Foo()/SafeFoo()/UnsafeFoo()Konvansiyonu kullanmayı seviyorum :

  • Fooyöntemler UnsafeFoovarsayılan olarak çağırır .
  • UnsafeFoo yöntemler durumu bir kilit olmadan serbestçe değiştirir, yalnızca diğer güvenli olmayan yöntemleri çağırmalıdırlar.
  • SafeFooyöntemler UnsafeFoobir kilit içindeki yöntemleri çağırır .

Biraz ayrıntılıdır, ancak iş parçacığı açısından güvenli olduğu varsayılan bir yöntemde bir kilit dışında güvenli olmayan yöntemleri çağırmak gibi, daha belirgin hatalar yapar.


5

İşte benim sabit boyutlu Kuyruğum

CountÖzellik kullanıldığında senkronizasyon ek yükünü önlemek için normal Kuyruk kullanır ConcurrentQueue. Ayrıca IReadOnlyCollectionLINQ yöntemlerinin kullanılabilmesi için uygular . Gerisi buradaki diğer cevaplara çok benziyor.

[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

Sırf eğlence olsun diye, burada yorum yapanların endişelerinin çoğunu ele aldığına inandığım başka bir uygulama var. Özellikle, iş parçacığı güvenliği kilitleme olmadan elde edilir ve uygulama, sarma sınıfı tarafından gizlenir.

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
Bu eşzamanlı olarak kullanılırsa bozulur - ya bir evre çağrıldıktan sonra _queue.Enqueue(obj)ama önce Interlocked.Increment(ref _count)önceliklendirilirse ve diğer evre çağırırsa .Count? Yanlış sayılır. Diğer sorunları kontrol etmedim.
KFL

3

Benim versiyonum normal Queueolanların sadece bir alt sınıfı … özel bir şey değil ama herkesin katılımını görmek ve yine de buraya koyabileceğim konu başlığı ile devam ediyor. Ayrıca her ihtimale karşı kuyruğu alınmış olanları da döndürür.

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

Bir cevap daha ekleyelim. Neden bu diğerleri üzerinde?

1) Basitlik. Boyutu garanti etmeye çalışmak iyi ve iyidir, ancak kendi sorunlarını sergileyebilecek gereksiz karmaşıklığa yol açar.

2) IReadOnlyCollection'ı uygular, yani üzerinde Linq kullanabilir ve onu IEnumerable'ı bekleyen çeşitli şeylere aktarabilirsiniz.

3) Kilitleme yok. Yukarıdaki çözümlerin çoğu, kilitleri kullanır ve bu, kilitsiz bir koleksiyonda yanlıştır.

4) IProducerConsumerCollection da dahil olmak üzere ConcurrentQueue'nun yaptığı aynı yöntem, özellik ve arabirim kümesini uygular; bu, koleksiyonu BlockingCollection ile kullanmak istiyorsanız önemlidir.

TryDequeue başarısız olursa, bu uygulama potansiyel olarak beklenenden daha fazla girdiyle sonuçlanabilir, ancak bunun meydana gelme sıklığı, kaçınılmaz olarak performansı engelleyecek ve kendi beklenmedik sorunlarına neden olacak özel kodlara değmez.

Bir boyutu kesinlikle garanti etmek istiyorsanız, bir Prune () veya benzer bir yöntemi uygulamak en iyi fikir gibi görünüyor. Diğer yöntemlerde (TryDequeue dahil) ReaderWriterLockSlim okuma kilidi kullanabilir ve yalnızca budama sırasında bir yazma kilidi alabilirsiniz.

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

Henüz kimse söylemediği için .. a kullanabilir LinkedList<T>ve iş parçacığı güvenliğini ekleyebilirsiniz:

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

Unutulmaması gereken bir nokta, bu örnekte varsayılan numaralandırma sırasının LIFO olacağıdır. Ancak gerekirse bu geçersiz kılınabilir.


1

Kodlama zevkiniz için size ' 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();
       }
   }
}

Örnek Kullanım:

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
Bu uygulamayı beğendim ancak hiçbiri eklenmediğinde varsayılan (T)
Daniel Leach

Kilidi bu şekilde kullanırsanız, okuyucularınıza öncelik vermek için ReaderWriterLockSlim kullanmalısınız.
Josh

1

Peki, kullanıma bağlı olarak, yukarıdaki çözümlerden bazılarının çok iş parçacıklı ortamda kullanıldığında boyutu aşabileceğini fark ettim. Her neyse, benim kullanım durumum son 5 olayı göstermekti ve kuyruğa olay yazan birden çok iş parçacığı ve ondan okuyan ve Winform Kontrolünde görüntüleyen başka bir iş parçacığı var. Yani bu benim çözümümdü.

DÜZENLEME: Uygulamamızda zaten kilitlemeyi kullandığımız için ConcurrentQueue'ya gerçekten ihtiyacımız olmadığı için performansı artırabilir.

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

DÜZENLEME: syncObjectYukarıdaki örneğe gerçekten ihtiyacımız yok ve queuenesneyi kullanmayı tercih edebiliriz çünkü queueherhangi bir işlevi yeniden başlatmıyoruz ve readonlyzaten olarak işaretleniyor .


0

Kabul edilen yanıt, önlenebilir yan etkilere sahip olacak.

İnce Taneli Kilitleme ve Kilitsiz Mekanizmalar

Aşağıdaki bağlantılar, aşağıdaki örneğimi yazdığımda kullandığım referanslardır.

Microsoft'un belgeleri bir kilit kullandıkları için biraz yanıltıcı olsa da, ancak segment sınıflarını kilitliyorlar. Segment sınıflarının kendileri Interlocked kullanır.

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

Burada, ConcurrentQueue aracılığıyla sağlanan aynı arabirimleri sağlarken, temeldeki ConcurrentQueue'yu olabildiğince çok kullanan başka bir uygulama vardır.

/// <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

Bu benim kuyruk versiyonum:

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

Bir IEnumerable üzerine inşa edilmiş bir kurucuya sahip olmayı yararlı buluyorum ve arama anında öğelerin çok iş parçacıklı bir güvenli listesine (bu durumda dizi) sahip olmak için bir GetSnapshot'a sahip olmayı yararlı buluyorum, bu yükselmiyor altta yatan koleksiyon değişirse hatalar.

Çift Sayım kontrolü, bazı durumlarda kilidi önlemek içindir.


1
Sırayı kilitlemek için oy kullanıyorum. Kesinlikle kilitlemek istiyorsanız, bir ReaderWriterLockSlim en iyisidir (yazma kilidinden daha sık bir okuma kilidi almayı beklediğiniz varsayılarak). GetSnapshot da gerekli değildir. IReadOnlyCollection <T> (IEnumerable semantiği için yapmanız gereken) uygularsanız, ToList () aynı işlevi görür.
Josh

ConcurrentQueue, uygulamasındaki kilitleri ele alır, cevabımdaki bağlantılara bakın.
jjhayter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.