.NET में एक अवरुद्ध कतार <T> बनाना?


163

मेरे पास एक परिदृश्य है जहां मेरे पास एक कतार में कई धागे हैं और एक ही कतार से कई धागे पढ़ने हैं। यदि कतार एक विशिष्ट आकार तक पहुँचती है, तो कतार को भरने वाले सभी धागे कतार पर एक आइटम को हटाए जाने तक जोड़ पर अवरुद्ध हो जाएंगे।

नीचे दिए गए समाधान जो मैं अभी उपयोग कर रहा हूं और मेरा प्रश्न है: इसमें सुधार कैसे किया जा सकता है? क्या कोई ऐसी वस्तु है जो पहले से ही बीसीएल में इस व्यवहार को सक्षम करती है जिसे मुझे उपयोग करना चाहिए?

internal class BlockingCollection<T> : CollectionBase, IEnumerable
{
    //todo: might be worth changing this into a proper QUEUE

    private AutoResetEvent _FullEvent = new AutoResetEvent(false);

    internal T this[int i]
    {
        get { return (T) List[i]; }
    }

    private int _MaxSize;
    internal int MaxSize
    {
        get { return _MaxSize; }
        set
        {
            _MaxSize = value;
            checkSize();
        }
    }

    internal BlockingCollection(int maxSize)
    {
        MaxSize = maxSize;
    }

    internal void Add(T item)
    {
        Trace.WriteLine(string.Format("BlockingCollection add waiting: {0}", Thread.CurrentThread.ManagedThreadId));

        _FullEvent.WaitOne();

        List.Add(item);

        Trace.WriteLine(string.Format("BlockingCollection item added: {0}", Thread.CurrentThread.ManagedThreadId));

        checkSize();
    }

    internal void Remove(T item)
    {
        lock (List)
        {
            List.Remove(item);
        }

        Trace.WriteLine(string.Format("BlockingCollection item removed: {0}", Thread.CurrentThread.ManagedThreadId));
    }

    protected override void OnRemoveComplete(int index, object value)
    {
        checkSize();
        base.OnRemoveComplete(index, value);
    }

    internal new IEnumerator GetEnumerator()
    {
        return List.GetEnumerator();
    }

    private void checkSize()
    {
        if (Count < MaxSize)
        {
            Trace.WriteLine(string.Format("BlockingCollection FullEvent set: {0}", Thread.CurrentThread.ManagedThreadId));
            _FullEvent.Set();
        }
        else
        {
            Trace.WriteLine(string.Format("BlockingCollection FullEvent reset: {0}", Thread.CurrentThread.ManagedThreadId));
            _FullEvent.Reset();
        }
    }
}

5
.Net कैसे इस परिदृश्य के साथ मदद करने के लिए अंतर्निहित कक्षाएं हैं। यहां सूचीबद्ध अधिकांश उत्तर अप्रचलित हैं। सबसे हाल के जवाब नीचे देखें। थ्रेड-सुरक्षित ब्लॉकिंग संग्रह में देखें। उत्तर अप्रचलित हो सकते हैं, लेकिन यह अभी भी एक अच्छा सवाल है!
टॉम ए

मुझे लगता है कि अभी भी मॉनिटर.वेट / पल्स / पल्सएल के बारे में सीखना एक अच्छा विचार है, भले ही हमारे पास .NET में नए समवर्ती वर्ग हों।
thewpfguy

1
@Thewpfguy से सहमत हैं। आप पर्दे के पीछे बुनियादी लॉकिंग तंत्र को समझना चाहेंगे। यह भी ध्यान देने योग्य है कि Systems.Collections.Concurrent अप्रैल 2010 तक मौजूद नहीं था और उसके बाद केवल विजुअल स्टूडियो 2010 और इसके बाद के संस्करण में। निश्चित रूप से VS2008 होल्ड आउट के लिए विकल्प नहीं ...
विक

यदि आप इसे अभी पढ़ रहे हैं, तो एक प्रणाली देखें। एक बहु-लेखक / बहु-पाठक के लिए System.hreading.Channels, बंधे हुए, .NET कोर और .NET मानक के लिए इसे वैकल्पिक रूप से अवरुद्ध करने वाला कार्यान्वयन।
मार्क रेंडले

जवाबों:


200

यह बहुत असुरक्षित (बहुत कम तुल्यकालन) दिखता है; कैसे कुछ के बारे में:

class SizeQueue<T>
{
    private readonly Queue<T> queue = new Queue<T>();
    private readonly int maxSize;
    public SizeQueue(int maxSize) { this.maxSize = maxSize; }

    public void Enqueue(T item)
    {
        lock (queue)
        {
            while (queue.Count >= maxSize)
            {
                Monitor.Wait(queue);
            }
            queue.Enqueue(item);
            if (queue.Count == 1)
            {
                // wake up any blocked dequeue
                Monitor.PulseAll(queue);
            }
        }
    }
    public T Dequeue()
    {
        lock (queue)
        {
            while (queue.Count == 0)
            {
                Monitor.Wait(queue);
            }
            T item = queue.Dequeue();
            if (queue.Count == maxSize - 1)
            {
                // wake up any blocked enqueue
                Monitor.PulseAll(queue);
            }
            return item;
        }
    }
}

(संपादित करें)

वास्तव में, आप कतार को बंद करने का एक तरीका चाहते हैं ताकि पाठक सफाई से बाहर निकलना शुरू कर दें - शायद एक बूल ध्वज की तरह कुछ - अगर सेट किया गया है, तो एक खाली कतार बस वापस आती है (अवरुद्ध करने के बजाय):

bool closing;
public void Close()
{
    lock(queue)
    {
        closing = true;
        Monitor.PulseAll(queue);
    }
}
public bool TryDequeue(out T value)
{
    lock (queue)
    {
        while (queue.Count == 0)
        {
            if (closing)
            {
                value = default(T);
                return false;
            }
            Monitor.Wait(queue);
        }
        value = queue.Dequeue();
        if (queue.Count == maxSize - 1)
        {
            // wake up any blocked enqueue
            Monitor.PulseAll(queue);
        }
        return true;
    }
}

1
कैसे एक WaitAny करने के लिए प्रतीक्षा को बदलने और निर्माण पर एक समाप्त वेटहैंड में गुजर ...
सैम केसर

1
@ मार्क- एक अनुकूलन, यदि आप कतार से हमेशा क्षमता तक पहुंचने की उम्मीद कर रहे थे, तो कतार <T> के निर्माता में अधिकतम मान को पास करना होगा। आप अपनी कक्षा में उस के लिए अन्य निर्माता को जोड़ सकते हैं।
रिचर्ड

3
क्यों SizeQueue, क्यों नहीं FixedSizeQueue?
माइंडलेस.पांडा

4
@Lasse - यह दौरान लॉक (एस) को रिलीज़ करता है Wait, इसलिए अन्य धागे इसे हासिल कर सकते हैं। जब यह उठता है तो यह लॉक (ओं) को फिर से बताता है।
मार्क ग्रेवल

1
अच्छा, जैसा कि मैंने कहा, कुछ ऐसा था जो मुझे नहीं मिल रहा था :) यह सुनिश्चित करता है कि मैं अपने थ्रेड-कोड में से कुछ को फिर से देखना चाहता हूं ....
लास वी। कार्लसन

59

.Net 4 BlockingCollection का उपयोग करें, Enqueue का उपयोग करें (), toque का उपयोग करें () करने के लिए करें। यह आंतरिक रूप से गैर-अवरोधक समवर्ती क्यू का उपयोग करता है। यहाँ अधिक जानकारी फास्ट एंड बेस्ट प्रोड्यूसर / कंज्यूमर कतार तकनीक ब्लॉकिंगकॉलेक्शन बनाम समवर्ती कतार


14

"इसमें कैसे सुधार किया जा सकता है?"

ठीक है, आपको अपनी कक्षा में प्रत्येक विधि को देखना होगा और विचार करना होगा कि क्या होगा यदि एक और धागा उस पद्धति या किसी अन्य विधि को एक साथ बुला रहा था। उदाहरण के लिए, आप निकालें विधि में लॉक लगाते हैं, लेकिन ऐड विधि में नहीं। यदि एक धागा एक ही समय में एक और धागा निकालता है तो क्या होता है?बुरी चीजें।

यह भी विचार करें कि एक विधि एक दूसरी वस्तु को वापस कर सकती है जो पहले ऑब्जेक्ट के आंतरिक डेटा तक पहुंच प्रदान करती है - उदाहरण के लिए, गेटनेमर। कल्पना कीजिए कि एक धागा उस एन्यूमरेटर से गुजर रहा है, एक और धागा उसी समय सूची को संशोधित कर रहा है। अच्छा नही।

अंगूठे का एक अच्छा नियम यह है कि कक्षा में विधियों की संख्या को पूर्णतम तक घटाकर इसे सरल बनाया जाए।

विशेष रूप से, दूसरे कंटेनर वर्ग को इनहेरिट नहीं करें, क्योंकि आप उस क्लास के सभी तरीकों का खुलासा करेंगे, जिससे कॉल करने वाले को आंतरिक डेटा को दूषित करने, या डेटा को आंशिक रूप से पूर्ण परिवर्तन देखने के लिए एक रास्ता मिल जाएगा (क्योंकि डेटा ख़राब है, क्योंकि उस क्षण दूषित दिखाई देता है)। सभी विवरण छिपाएं और इस बारे में पूरी तरह निर्मम रहें कि आप उन तक कैसे पहुँच की अनुमति देते हैं।

मैं आपको सलाह दूंगा कि आप ऑफ-द-शेल्फ समाधानों का उपयोग करें - थ्रेडिंग के बारे में एक पुस्तक प्राप्त करें या तीसरे पक्ष के पुस्तकालय का उपयोग करें। अन्यथा, यह देखते हुए कि आप क्या प्रयास कर रहे हैं, आप लंबे समय तक अपना कोड डीबग करते रहेंगे।

इसके अलावा, किसी आइटम को वापस करने के लिए निकालें का अधिक अर्थ नहीं होगा (कहते हैं, वह जो पहले जोड़ा गया था, क्योंकि यह एक कतार है), बजाय एक विशिष्ट आइटम चुनने वाले कॉलर के? और जब कतार खाली होती है, तो शायद निकालें को भी ब्लॉक करना चाहिए।

अद्यतन: मार्क का जवाब वास्तव में इन सभी सुझावों को लागू करता है! :) लेकिन मैं इसे यहां छोड़ दूंगा क्योंकि यह समझना उपयोगी हो सकता है कि उसका संस्करण ऐसा सुधार क्यों है।


12

आप System.Collections.Concurrent नामस्थान में BlockingCollection और ConcurrentQueue का उपयोग कर सकते हैं

 public class ProducerConsumerQueue<T> : BlockingCollection<T>
{
    /// <summary>
    /// Initializes a new instance of the ProducerConsumerQueue, Use Add and TryAdd for Enqueue and TryEnqueue and Take and TryTake for Dequeue and TryDequeue functionality
    /// </summary>
    public ProducerConsumerQueue()  
        : base(new ConcurrentQueue<T>())
    {
    }

  /// <summary>
  /// Initializes a new instance of the ProducerConsumerQueue, Use Add and TryAdd for Enqueue and TryEnqueue and Take and TryTake for Dequeue and TryDequeue functionality
  /// </summary>
  /// <param name="maxSize"></param>
    public ProducerConsumerQueue(int maxSize)
        : base(new ConcurrentQueue<T>(), maxSize)
    {
    }



}

3
अवरोधन कतार के लिए चूक। इसलिए, मुझे नहीं लगता कि यह आवश्यक है।
कर्टिस व्हाइट

क्या ब्लॉकिंगकॉलिनेशन एक कतार की तरह आदेश को संरक्षित करता है?
जोएलक

हां, जब इसे एक समवर्ती क्यू के साथ शुरू किया जाता है
एंड्रियास

6

मैंने रिएक्टिव एक्सटेंशन्स का उपयोग करते हुए इसे खटखटाया और इस प्रश्न को याद किया:

public class BlockingQueue<T>
{
    private readonly Subject<T> _queue;
    private readonly IEnumerator<T> _enumerator;
    private readonly object _sync = new object();

    public BlockingQueue()
    {
        _queue = new Subject<T>();
        _enumerator = _queue.GetEnumerator();
    }

    public void Enqueue(T item)
    {
        lock (_sync)
        {
            _queue.OnNext(item);
        }
    }

    public T Dequeue()
    {
        _enumerator.MoveNext();
        return _enumerator.Current;
    }
}

जरूरी नहीं कि पूरी तरह से सुरक्षित हो, लेकिन बहुत सरल।


विषय <t> क्या है? मेरे पास इसके नाम स्थान के लिए कोई रिज़ॉल्वर नहीं है।
theJerm

यह रिएक्टिव एक्सटेंशन्स का हिस्सा है।
मार्क रेंडले

उत्तर नहीं। इस सवाल का जवाब बिल्कुल नहीं है।
मखदुम

5

यह वही है जो मैं एक थ्रेड सुरक्षित बाउंड अवरुद्ध कतार के लिए आया था।

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

public class BlockingBuffer<T>
{
    private Object t_lock;
    private Semaphore sema_NotEmpty;
    private Semaphore sema_NotFull;
    private T[] buf;

    private int getFromIndex;
    private int putToIndex;
    private int size;
    private int numItems;

    public BlockingBuffer(int Capacity)
    {
        if (Capacity <= 0)
            throw new ArgumentOutOfRangeException("Capacity must be larger than 0");

        t_lock = new Object();
        buf = new T[Capacity];
        sema_NotEmpty = new Semaphore(0, Capacity);
        sema_NotFull = new Semaphore(Capacity, Capacity);
        getFromIndex = 0;
        putToIndex = 0;
        size = Capacity;
        numItems = 0;
    }

    public void put(T item)
    {
        sema_NotFull.WaitOne();
        lock (t_lock)
        {
            while (numItems == size)
            {
                Monitor.Pulse(t_lock);
                Monitor.Wait(t_lock);
            }

            buf[putToIndex++] = item;

            if (putToIndex == size)
                putToIndex = 0;

            numItems++;

            Monitor.Pulse(t_lock);

        }
        sema_NotEmpty.Release();


    }

    public T take()
    {
        T item;

        sema_NotEmpty.WaitOne();
        lock (t_lock)
        {

            while (numItems == 0)
            {
                Monitor.Pulse(t_lock);
                Monitor.Wait(t_lock);
            }

            item = buf[getFromIndex++];

            if (getFromIndex == size)
                getFromIndex = 0;

            numItems--;

            Monitor.Pulse(t_lock);

        }
        sema_NotFull.Release();

        return item;
    }
}

क्या आप इस लायब्रेरी के उपयोग से कुछ थ्रेड फ़ंक्शंस को कतारबद्ध करने के कुछ कोड नमूने प्रदान कर सकते हैं, जिसमें यह भी शामिल है कि मैं इस क्लास को कैसे इंस्टेंट करूँ?
theJerm

यह प्रश्न / प्रतिक्रिया थोड़ी दिनांकित है। आपको कतार समर्थन को रोकने के लिए System.Collections.Concurrent नाम स्थान पर देखना चाहिए।
केविन

2

मैंने पूरी तरह से टीपीएल का पता नहीं लगाया है, लेकिन उनके पास कुछ ऐसा हो सकता है जो आपकी आवश्यकताओं को पूरा करता है, या बहुत कम से कम, कुछ परावर्तक चारे से कुछ प्रेरणा लेने के लिए।

उम्मीद है की वो मदद करदे।


मुझे पता है कि यह पुराना है, लेकिन मेरी टिप्पणी एसओ के लिए नए लोगों के लिए है, क्योंकि ओपी आज यह पहले से ही जानता है। यह कोई जवाब नहीं है, यह टिप्पणी होनी चाहिए थी।
जॉन डेमेट्रियौ

0

ठीक है, आप System.Threading.Semaphoreकक्षा को देख सकते हैं । इसके अलावा - नहीं, आपको इसे खुद बनाना होगा। AFAIK ऐसा कोई अंतर्निहित संग्रह नहीं है।


मैंने उस थ्रेड की संख्या को देखने के लिए देखा जो एक संसाधन तक पहुंच रहे हैं, लेकिन यह आपको किसी स्थिति (जैसे संग्रह.काउंट) के आधार पर संसाधन तक सभी पहुंच को अवरुद्ध करने की अनुमति नहीं देता है। AFAIK वैसे भी
एरिक Schoonover

ठीक है, आप उस भाग को अपने साथ करते हैं, जैसे आप अभी करते हैं। बस MaxSize और _FullEvent के बजाय आपके पास Semaphore है, जिसे आप कंस्ट्रक्टर में सही गणना के साथ शुरू करते हैं। फिर, प्रत्येक जोड़ें / निकालें पर आप WaitForOne () या रिलीज़ () कहते हैं।
विल्क्स-

अब आपके पास जो है उससे यह बहुत अलग नहीं है। बस और सरल IMHO।
विल्क्स-

क्या आप मुझे इस काम को दिखाते हुए एक उदाहरण दे सकते हैं? मैंने यह नहीं देखा कि सेमीफोर के आकार को गतिशील रूप से कैसे समायोजित किया जाए जिसके लिए इस परिदृश्य की आवश्यकता है। चूँकि आपको सभी संसाधनों को ब्लॉक करने में सक्षम होना है, यदि कतार पूरी हो।
एरिक शूनओवर

आह, बदलते आकार! आपने तुरंत ऐसा क्यों नहीं कहा? ठीक है, तो एक अर्धकुंभ आपके लिए नहीं है। इस दृष्टिकोण के साथ शुभकामनाएँ!
विल्क्स- २

-1

यदि आप अधिकतम थ्रूपुट चाहते हैं, तो कई पाठकों को पढ़ने और केवल एक लेखक को लिखने की अनुमति देता है, BCL के पास ReaderWriterLockSlim नामक कुछ है जो आपके कोड को धीमा करने में मदद करेगा ...


मैं चाहता हूं कि कोई भी नहीं लिख पा रहा है, हालांकि कतार भरी हुई है।
एरिक शूनओवर 22

तो आप इसे एक लॉक के साथ जोड़ दें। यहाँ कुछ बहुत अच्छे उदाहरण हैं albahari.com/threading/part2.aspx#_ProducerConsumerQWaitHandle albahari.com/threading/part4.aspx
DavidN

3
कतार / विपंक्ति से सभी लोग एक लेखक है ... एक विशेष ताला शायद अधिक व्यावहारिक हो जाएगा
मार्क Gravell

मुझे पता है कि यह पुराना है, लेकिन मेरी टिप्पणी एसओ के लिए नए लोगों के लिए है, क्योंकि ओपी आज यह पहले से ही जानता है। यह कोई जवाब नहीं है, यह टिप्पणी होनी चाहिए थी।
जॉन डेमेट्रियौ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.