निश्चित आकार की कतार जो नए एनेक्स पर पुराने मूल्यों को स्वचालित रूप से समाप्त कर देती है


121

मैं ConcurrentQueueएक साझा डेटा संरचना का उपयोग कर रहा हूं, जिसका उद्देश्य अंतिम एन वस्तुओं को रखा गया है जो इस तरह का (इतिहास का) है।

मान लें कि हमारे पास एक ब्राउज़र है और हम पिछले 100 ब्राउज्ड उरल्स चाहते हैं। मुझे एक कतार चाहिए जो स्वचालित रूप से नए प्रविष्टि प्रविष्टि (एन्केयू) पर सबसे पुरानी (पहली) प्रविष्टि को गिरा देती है जब क्षमता पूरी हो जाती है (इतिहास में 100 पते)।

मैं उस उपयोग को कैसे पूरा कर सकता हूं System.Collections?



यह विशेष रूप से आपके लिए नहीं था, लेकिन जो कोई भी इस सवाल पर आता है और वह इसे उपयोगी पा सकता है। btw, यह C # के बारे में भी बात करता है। क्या आपने सभी उत्तरों (2 मिनट में) को पढ़ने का प्रबंधन किया और यह पता लगाया कि वहां कोई C # कोड नहीं है? वैसे भी, मुझे खुद पर यकीन नहीं है, और इसलिए यह एक टिप्पणी है ...

आप केवल तरीकों को लॉक में लपेट सकते हैं। यह देखते हुए कि वे तेज़ हैं, आप पूरे सरणी को लॉक कर सकते हैं। यह शायद एक डुबकी है, हालांकि। सी # कोड के साथ परिपत्र बफर कार्यान्वयन के लिए खोज आपको कुछ मिल सकता है। वैसे भी, सौभाग्य।

जवाबों:


111

मैं एक आवरण वर्ग लिखूंगा कि जब गणना सीमा से अधिक हो जाती है तो एन्क्यू पर चेक और फिर डीक्यू की जांच करें।

 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ऑब्जेक्ट के लिए निजी है, ताकि lockअन्य थ्रेड्स को एक साथ एक्सेस करने से रोका जा सके।
रिचर्ड श्नाइडर

14
ताला लगाना अच्छा नहीं है। BCL समवर्ती संग्रह का पूरा उद्देश्य प्रदर्शन के लिए लॉक फ्री कंसीडर प्रदान करना है। आपके कोड में लॉकिंग उस लाभ से समझौता करता है। वास्तव में मुझे एक कारण नहीं दिखता है कि आपको डीएके को लॉक करने की आवश्यकता है।
KFL

2
@ केएफएल को लॉक करने की आवश्यकता है क्योंकि Countऔर TryDequeueदो स्वतंत्र संचालन हैं जो देखभाल करते हैं कि बीसीएल समवर्ती द्वारा सिंक नहीं किया गया है।
रिचर्ड श्नाइडर

9
@RichardSchneider यदि आपको स्वयं समसामयिक मुद्दों को संभालने की आवश्यकता है, तो किसी ConcurrentQueue<T>वस्तु के लिए ऑब्जेक्ट को स्वैप करना एक अच्छा विचार होगा Queue<T>जो अधिक हल्का है।
0b101010

6
अपनी खुद की कतार को परिभाषित न करें, बस विरासत में मिला उपयोग करें। यदि आप ऐसा करते हैं, तो आप वास्तव में कतार मूल्यों, अन्य सभी कार्यों के साथ और कुछ नहीं कर सकते हैं लेकिन आपका नया Enqueueअभी भी मूल कतार कहेगा। दूसरे शब्दों में, यद्यपि यह उत्तर स्वीकृत के रूप में चिह्नित है, यह पूरी तरह से और पूरी तरह से टूट गया है।
गैबोर

104

मैं एक मामूली संस्करण के लिए जाऊँगा ... समवर्ती क्यूब्यू का विस्तार करें ताकि फिक्स्ड एक्सटेंशन पर लाइनक एक्सटेंशन का उपयोग कर सकें

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
जब कोई व्यक्ति सांख्यिकीय रूप से एक समवर्ती क्यू <T> के रूप में जानता है तो क्या होता है, उन्होंने आपके 'नए' कीवर्ड को दरकिनार कर दिया है।
mhand

6
@ मन्द अगर 'कोई' ऐसा करना चाहता था; फिर उन्होंने एक समवर्ती क्यू <<> वस्तु के साथ शुरू करने के लिए चुना होगा ... यह एक कस्टम स्टोरेज क्लास है। कोई भी इसके लिए .NET फ्रेमवर्क को प्रस्तुत करने की मांग नहीं कर रहा है। आपने इसके लिए एक समस्या बनाने की कोशिश की है।
डेव लॉरेंस

9
मेरी बात उपशमन के बजाय हो सकती है कि आपको बस कतार को लपेटना चाहिए ... यह सभी मामलों में वांछित व्यवहार को लागू करता है। इसके अलावा, चूंकि यह एक कस्टम स्टोरेज क्लास है, इसलिए इसे पूरी तरह से कस्टम बनाते हैं, केवल उन ऑपरेशन्स को उजागर करते हैं जिनकी हमें जरूरत है, सबक्लासिंग यहां गलत टूल है IMHO।
mhand

3
@ मेहँदी हाँ मुझे वही मिल रहा है जो आप कह रहे हैं .. मैं एक कतार लपेट सकता हूं और कतार के प्रगणक को उजागर कर सकता हूं ताकि लिनक एक्सटेंशन का उपयोग किया जा सके।
डेव लॉरेंस

1
मैं सहमत हूँ @ के साथ कि आप को समवर्ती क्यू नहीं होना चाहिए क्योंकि एन्क्यू विधि आभासी नहीं है। आपको कतार को प्रॉक्सी करना चाहिए और यदि वांछित है तो पूरे इंटरफ़ेस को लागू करना चाहिए।
क्रिस मैरिसिक

29

जो कोई भी इसे उपयोगी पाता है, उसके लिए यहां रिचर्ड श्नाइडर के उत्तर के आधार पर कुछ कार्य कोड दिए गए हैं:

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
उल्लिखित कारणों के लिए वोटिंग (एक समवर्ती क्यू का उपयोग करते समय लॉक करना खराब है) इसके अलावा किसी भी आवश्यक इंटरफेस को लागू न करने के लिए यह एक सच्चा संग्रह है।
जोश

11

इसके लायक के लिए, यहाँ सुरक्षित और असुरक्षित उपयोग के लिए चिह्नित कुछ तरीकों के साथ एक हल्के परिपत्र बफर है।

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

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

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

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

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

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

    #region Count

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

    #endregion

    #region Enqueue

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

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

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

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

    #endregion

    #region Dequeue

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

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

    public T UnsafeDequeue()
    {
        UnsafeEnsureQueueNotEmpty();

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

        return res;
    }

    #endregion

    #region Peek

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

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

    public T UnsafePeek()
    {
        UnsafeEnsureQueueNotEmpty();

        return values[head];
    }

    #endregion


    #region GetEnumerator

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

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

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

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

    #endregion
}

मुझे Foo()/SafeFoo()/UnsafeFoo()सम्मेलन का उपयोग करना पसंद है :

  • Fooतरीके UnsafeFooडिफ़ॉल्ट के रूप में कहते हैं ।
  • UnsafeFoo विधियां बिना किसी लॉक के स्वतंत्र रूप से राज्य को संशोधित करती हैं, उन्हें केवल अन्य असुरक्षित तरीकों को कॉल करना चाहिए।
  • SafeFooतरीके UnsafeFooएक लॉक के अंदर तरीकों को कॉल करते हैं।

इसकी थोड़ी सी क्रिया है, लेकिन यह स्पष्ट त्रुटियां करता है, जैसे कि एक विधि के बाहर एक असुरक्षित तरीके से कॉल करना, जिसे थ्रेड-सुरक्षित माना जाता है, अधिक स्पष्ट।


5

यहाँ मेरा आकार तय कतार पर है

जब Countसंपत्ति का उपयोग किया जाता है तो यह सिंक्रनाइज़ेशन ओवरहेड से बचने के लिए नियमित कतार का उपयोग करता है ConcurrentQueue। यह भी लागू करता है IReadOnlyCollectionताकि LINQ विधियों का उपयोग किया जा सके। बाकी यहां के अन्य उत्तरों के समान है।

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

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

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

        Limit = limit;
    }

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

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

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

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

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

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

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

3

बस मज़े के लिए, यहां एक और कार्यान्वयन है जो मेरा मानना ​​है कि अधिकांश टिप्पणीकारों की चिंताओं को संबोधित करता है। विशेष रूप से, थ्रेड-सुरक्षा लॉक किए बिना प्राप्त की जाती है और कार्यान्वयन रैपिंग क्लास द्वारा छिपाया जाता है।

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
यदि समवर्ती रूप से उपयोग किया जाता है तो यह टूट गया है - क्या होगा यदि एक थ्रेड को कॉल करने के बाद _queue.Enqueue(obj)पहले Interlocked.Increment(ref _count)और दूसरे थ्रेड को कॉल किया जाता है .Count? यह एक गलत गणना होगी। मैंने अन्य मुद्दों के लिए जाँच नहीं की है।
केएफएल

3

मेरा संस्करण सामान्य Queueलोगों का सिर्फ एक उपवर्ग है। कुछ भी खास नहीं है लेकिन सभी को भाग लेते हुए देखते हैं और यह अभी भी विषय शीर्षक के साथ जाता है जो मैं यहां डाल सकता हूं। यह सिर्फ मामले में ही हटाए गए लोगों को लौटाता है।

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

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

2

चलिए एक और जवाब जोड़ते हैं। यह दूसरों पर क्यों?

1) सादगी। आकार की गारंटी देने की कोशिश अच्छी और अच्छी है लेकिन अनावश्यक जटिलता की ओर जाता है जो अपनी समस्याओं को प्रदर्शित कर सकता है।

2) इम्प्लिमेंट्स IReadOnlyCollection, जिसका अर्थ है कि आप इस पर Linq का उपयोग कर सकते हैं और इसे कई प्रकार की चीजों में पास कर सकते हैं जो IEnumerable की अपेक्षा करते हैं।

3) कोई लॉकिंग नहीं। ऊपर दिए गए कई समाधान ताले का उपयोग करते हैं, जो लॉकलेस संग्रह पर गलत है।

4) समान तरीकों का सेट, गुण, और इंटरफेस समवर्ती queue करता है, जिसमें IProducerConsumerCollection शामिल है, जो महत्वपूर्ण है यदि आप संग्रह के साथ ब्लॉकिंगकॉल का उपयोग करना चाहते हैं।

यदि ट्रिपडेक्यू विफल रहता है, तो यह कार्यान्वयन संभावित रूप से अधिक प्रविष्टियों के साथ समाप्त हो सकता है, लेकिन होने वाली आवृत्ति विशेष कोड के लायक नहीं लगती है जो अनिवार्य रूप से प्रदर्शन में बाधा डालती है और अपनी अप्रत्याशित समस्याओं का कारण बनती है।

यदि आप पूरी तरह से एक आकार की गारंटी देना चाहते हैं, तो प्रून () या इसी तरह की विधि को लागू करना सबसे अच्छा विचार है। आप अन्य तरीकों (TryDequeue सहित) में ReaderWriterLockSlim रीड लॉक का उपयोग कर सकते हैं और प्रूनिंग करते समय केवल एक राइट लॉक ले सकते हैं।

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

सिर्फ इसलिए कि किसी ने अभी तक यह नहीं कहा है .. आप एक का उपयोग कर सकते हैं LinkedList<T>और थ्रेड सुरक्षा जोड़ सकते हैं :

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

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

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

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

ध्यान देने वाली एक बात यह है कि इस उदाहरण में डिफॉल्ट एन्यूमरेशन ऑर्डर LIFO होगा। लेकिन जरूरत पड़ने पर इसे ओवरराइड किया जा सकता है।


1

आपकी कोडिंग खुशी के लिए मैं आपको ' 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();
       }
   }
}

उदाहरण उपयोग:

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) लौटाता है
डैनियल लीच

यदि आप इस तरीके से लॉक का उपयोग करते हैं, तो आपको अपने पाठकों को प्राथमिकता देने के लिए ReaderWriterLockSlim का उपयोग करना चाहिए।
जोश

1

वैसे यह मेरे द्वारा उपयोग किए गए उपयोग पर निर्भर करता है कि उपरोक्त थ्रेडेड वातावरण में उपयोग किए जाने पर उपरोक्त कुछ समाधान आकार से अधिक हो सकते हैं। वैसे भी मेरा उपयोग मामला पिछली 5 घटनाओं को प्रदर्शित करने के लिए था और कतार में कई थ्रेड लिखने की घटनाएं हैं और एक अन्य थ्रेड को पढ़ने और इसे विन्फ़र कंट्रोल में प्रदर्शित करने के लिए। तो यह था मेरा उपाय।

संपादित करें: चूंकि हम पहले ही अपने कार्यान्वयन के भीतर लॉकिंग का उपयोग कर रहे हैं, हमें वास्तव में समवर्ती कतार की आवश्यकता नहीं है, इससे प्रदर्शन में सुधार हो सकता है।

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

संपादित करें: हमें वास्तव syncObjectमें उपरोक्त उदाहरण की आवश्यकता नहीं है और हम queueवस्तु का उपयोग कर सकते हैं क्योंकि हम queueकिसी भी फ़ंक्शन में पुनः आरंभ नहीं कर रहे हैं और इसके readonlyवैसे भी चिह्नित हैं ।


0

स्वीकृत उत्तर में दुष्परिणाम होने वाले हैं।

ललित-अनारक्षित ताला और ताला-मुक्त तंत्र

नीचे दिए गए लिंक वे संदर्भ हैं जिनका मैंने उपयोग किया था जब मैंने नीचे अपना उदाहरण लिखा था।

जबकि Microsoft से प्रलेखन थोड़ा भ्रामक है क्योंकि वे एक लॉक का उपयोग करते हैं लेकिन वे सेगमेंट कक्षाओं को लॉक करते हैं। सेगमेंट की कक्षाएं खुद इंटरलाक्ड का उपयोग करती हैं।

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

यहाँ अभी तक एक और कार्यान्वयन है जो अंतर्निहित समवर्ती क्यूब्यू का उपयोग करता है, जबकि समवर्ती क्यू के माध्यम से उपलब्ध कराए गए समान इंटरफेस प्रदान करते हुए संभव है।

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

यह कतार का मेरा संस्करण है:

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

मुझे ऐसा कंस्ट्रक्टर मिलता है जो एक IEnumerable पर बनाया गया है और मुझे यह उपयोगी लगता है कि GetSnapshot को कॉल के समय आइटमों की बहु-सुरक्षित सुरक्षित सूची (इस मामले में सरणी) के लिए उपयोगी होना चाहिए, जो नहीं बढ़ता है यदि अंडरलेइंग संग्रह में त्रुटियां बदलती हैं।

डबल काउंट चेक कुछ परिस्थितियों में लॉक को रोकने के लिए है।


1
कतार में ताला लगाने के लिए मतदान। यदि आप पूरी तरह से लॉक करना चाहते हैं, तो एक ReaderWriterLockSlim सबसे अच्छा होगा (यह मानते हुए कि आप रीड लॉक की तुलना में अधिक बार रीड लॉक लेने की उम्मीद करते हैं)। GetSnapshot को भी ज़रूरत नहीं है। यदि आप IReadOnlyCollection <T> (जो आपको IEnumerable शब्दार्थ के लिए चाहिए) लागू करते हैं, तो ToList () एक ही कार्य करेगा।
जोश

समवर्ती क्यू इसके कार्यान्वयन में ताले को संभालता है, मेरे उत्तर में लिंक देखें।
२०:२६ पर जेजेथर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.