क्या अतुल्यकालिक अवरोधन <T> जैसा कुछ भी है?


86

मैं एसिंक्रोनस रूप awaitसे परिणाम पर चाहूंगा BlockingCollection<T>.Take(), इसलिए मैं धागे को अवरुद्ध नहीं करता हूं। कुछ इस तरह की तलाश में:

var item = await blockingCollection.TakeAsync();

मुझे पता है कि मैं यह कर सकता था:

var item = await Task.Run(() => blockingCollection.Take());

लेकिन यह थोड़े पूरे विचार को मार देता है, क्योंकि ThreadPoolइसके बजाय एक और धागा ( अवरुद्ध) हो जाता है।

क्या कोई विकल्प है?


2
मुझे यह नहीं मिलता है, यदि आप await Task.Run(() => blockingCollection.Take())कार्य का उपयोग अन्य थ्रेड पर करेंगे और आपका UI थ्रेड ब्लॉक नहीं होगा। क्या यह बात नहीं है?
सेलमैन जेनकी

8
@ Selman22, यह एक यूआई ऐप नहीं है। यह एक पुस्तकालय निर्यात- Taskआधारित एपीआई है। इसका उपयोग ASP.NET से किया जा सकता है, उदाहरण के लिए। प्रश्न में कोड वहाँ अच्छी तरह से पैमाने नहीं होगा।
एवो

अगर इसके बाद भी ConfigureAwaitइसका इस्तेमाल किया जाता है तो क्या यह एक समस्या है Run()? [ईडी। कोई बात नहीं, मैं देख रहा हूँ कि तुम अब क्या कह रहे हो]
मोजोफिल्टर

जवाबों:


96

चार विकल्प हैं जो मुझे पता हैं।

पहला चैनल है , जो थ्रेडसेफ़ कतार प्रदान करता है जो अतुल्यकालिक Readऔर Writeसंचालन का समर्थन करता है । चैनल अत्यधिक अनुकूलित होते हैं और वैकल्पिक रूप से कुछ वस्तुओं को गिराने का समर्थन करते हैं यदि एक सीमा तक पहुंच जाती है।

बगल में है BufferBlock<T>से TPL Dataflow । यदि आपके पास केवल एक एकल उपभोक्ता है, तो आप इसका उपयोग कर सकते हैं OutputAvailableAsyncया ReceiveAsyncइसे केवल एक से जोड़ सकते हैं ActionBlock<T>। अधिक जानकारी के लिए, मेरा ब्लॉग देखें

पिछले दो प्रकार हैं जो मैंने बनाए हैं, मेरे AsyncEx लाइब्रेरी में उपलब्ध हैं ।

AsyncCollection<T>के asyncसमतुल्य है BlockingCollection<T>, जैसे कि समवर्ती निर्माता / उपभोक्ता संग्रह को लपेटने में सक्षम ConcurrentQueue<T>या ConcurrentBag<T>। आप TakeAsyncसंग्रह से वस्तुओं का अतुल्यकालिक उपभोग करने के लिए उपयोग कर सकते हैं । अधिक जानकारी के लिए, मेरा ब्लॉग देखें

AsyncProducerConsumerQueue<T>अधिक पोर्टेबल- asyncअसंगत निर्माता / उपभोक्ता कतार है। आप DequeueAsyncकतार से आइटमों का अतुल्यकालिक उपभोग करने के लिए उपयोग कर सकते हैं । अधिक जानकारी के लिए, मेरा ब्लॉग देखें

इनमें से अंतिम तीन विकल्प सिंक्रोनस और एसिंक्रोनस पुट और लेता है।


12
जब कोडप्लेक्स अंत में बंद हो जाता है, उसके लिए Git हब लिंक: github.com/StephenCleary/AsyncEx
पॉल

एपीआई प्रलेखन में विधि शामिल है AsyncCollection.TryTakeAsync, लेकिन मैं इसे डाउनलोड Nito.AsyncEx.Coordination.dll 5.0.0.0(नवीनतम संस्करण) में नहीं पा सकता हूं । संदर्भित Nito.AsyncEx.Concurrent.dll पैकेज में मौजूद नहीं है । मैं क्या खो रहा हूँ?
थियोडोर ज़ूलियास

@ TheodorZoulias: उस विधि को v5 में हटा दिया गया था। V5 एपीआई डॉक्स यहां हैं
स्टीफन क्लीयर

धन्यवाद। ऐसा लगता है कि यह संग्रह को आसान बनाने का सबसे आसान और सुरक्षित तरीका था। while ((result = await collection.TryTakeAsync()).Success) { }। इसे क्यों हटाया गया?
थियोडोर ज़ूलियास

1
@ TheodorZoulias: क्योंकि "कोशिश" का मतलब अलग-अलग लोगों के लिए अलग-अलग चीजें हैं। मैं एक "कोशिश" विधि को वापस जोड़ने के बारे में सोच रहा हूं, लेकिन यह वास्तव में मूल विधि की तुलना में अलग शब्दार्थ होगा। भविष्य के संस्करण में async धाराओं का समर्थन करते हुए भी देख रहे हैं, जो निश्चित रूप से समर्थित होने पर खपत का सबसे अच्छा तरीका होगा।
स्टीफन क्लीयर

21

... या आप यह कर सकते हैं:

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class AsyncQueue<T>
{
    private readonly SemaphoreSlim _sem;
    private readonly ConcurrentQueue<T> _que;

    public AsyncQueue()
    {
        _sem = new SemaphoreSlim(0);
        _que = new ConcurrentQueue<T>();
    }

    public void Enqueue(T item)
    {
        _que.Enqueue(item);
        _sem.Release();
    }

    public void EnqueueRange(IEnumerable<T> source)
    {
        var n = 0;
        foreach (var item in source)
        {
            _que.Enqueue(item);
            n++;
        }
        _sem.Release(n);
    }

    public async Task<T> DequeueAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        for (; ; )
        {
            await _sem.WaitAsync(cancellationToken);

            T item;
            if (_que.TryDequeue(out item))
            {
                return item;
            }
        }
    }
}

सरल, पूरी तरह कार्यात्मक अतुल्यकालिक फीफो कतार।

नोट: SemaphoreSlim.WaitAsyncइससे पहले .NET 4.5 में जोड़ा गया था, यह सब सीधा नहीं था।


2
अनंत का उपयोग क्या है for? यदि सेमाफोर जारी किया जाता है, तो कतार में कम से कम एक आइटम को समाप्त करने के लिए है, नहीं?
ब्लेंडरस्टर

2
अगर कई उपभोक्ताओं को ब्लॉक किया जाता है तो @ बेलेंडर एक दौड़ की स्थिति हो सकती है। हम यह निश्चित रूप से नहीं जान सकते हैं कि कम से कम दो प्रतिस्पर्धी उपभोक्ता नहीं हैं और हम नहीं जानते कि दोनों एक आइटम को धोखा देने से पहले उठने का प्रबंधन करते हैं या नहीं। एक दौड़ की स्थिति में, यदि कोई छल करने में कामयाब नहीं होता है, तो वह वापस सो जाएगा और दूसरे सिग्नल की प्रतीक्षा करेगा।
जॉन लेडिग्रेन

यदि दो या दो से अधिक उपभोक्ता इसे वाट्सएप पर पिछले कर देते हैं (), तो कतार में बराबर संख्या में आइटम हैं, और इस प्रकार वे हमेशा सफलतापूर्वक समाप्त हो जाएंगे। क्या मैं कुछ भूल रहा हूँ?
माइंडक्रूजर

2
यह एक अवरुद्ध संग्रह है, के शब्दार्थ TryDequeueहैं, एक मान के साथ लौटें, या बिल्कुल न लौटें। तकनीकी रूप से, यदि आपके पास 1 से अधिक पाठक हैं, तो कोई भी पाठक पूरी तरह से जागने से पहले दो पाठक (या अधिक) वस्तुओं का उपभोग कर सकता है। एक सफल WaitAsyncकेवल एक संकेत है कि उपभोग करने के लिए कतार में आइटम हो सकते हैं, यह कोई गारंटी नहीं है।
जॉन लीडग्रेन

@JohnLeidegren If the value of the CurrentCount property is zero before this method is called, the method also allows releaseCount threads or tasks blocked by a call to the Wait or WaitAsync method to enter the semaphore.से docs.microsoft.com/en-us/dotnet/api/... कैसे एक सफल रहा है WaitAsyncकतार में आइटम नहीं? यदि N जारी है, तो N उपभोक्ताओं की तुलना semaphoreमें अधिक टूट गया है। है ना?
आशीष नेगी

4

यहाँ एक बहुत ही बुनियादी कार्यान्वयन है BlockingCollectionजो प्रतीक्षा का समर्थन करता है, जिसमें बहुत सारी गायब विशेषताएँ हैं। यह AsyncEnumerableपुस्तकालय का उपयोग करता है , जो 8.0 से अधिक पुराने # संस्करणों के लिए अतुल्यकालिक गणना को संभव बनाता है।

public class AsyncBlockingCollection<T>
{ // Missing features: cancellation, boundedCapacity, TakeAsync
    private Queue<T> _queue = new Queue<T>();
    private SemaphoreSlim _semaphore = new SemaphoreSlim(0);
    private int _consumersCount = 0;
    private bool _isAddingCompleted;

    public void Add(T item)
    {
        lock (_queue)
        {
            if (_isAddingCompleted) throw new InvalidOperationException();
            _queue.Enqueue(item);
        }
        _semaphore.Release();
    }

    public void CompleteAdding()
    {
        lock (_queue)
        {
            if (_isAddingCompleted) return;
            _isAddingCompleted = true;
            if (_consumersCount > 0) _semaphore.Release(_consumersCount);
        }
    }

    public IAsyncEnumerable<T> GetConsumingEnumerable()
    {
        lock (_queue) _consumersCount++;
        return new AsyncEnumerable<T>(async yield =>
        {
            while (true)
            {
                lock (_queue)
                {
                    if (_queue.Count == 0 && _isAddingCompleted) break;
                }
                await _semaphore.WaitAsync();
                bool hasItem;
                T item = default;
                lock (_queue)
                {
                    hasItem = _queue.Count > 0;
                    if (hasItem) item = _queue.Dequeue();
                }
                if (hasItem) await yield.ReturnAsync(item);
            }
        });
    }
}

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

var abc = new AsyncBlockingCollection<int>();
var producer = Task.Run(async () =>
{
    for (int i = 1; i <= 10; i++)
    {
        await Task.Delay(100);
        abc.Add(i);
    }
    abc.CompleteAdding();
});
var consumer = Task.Run(async () =>
{
    await abc.GetConsumingEnumerable().ForEachAsync(async item =>
    {
        await Task.Delay(200);
        await Console.Out.WriteAsync(item + " ");
    });
});
await Task.WhenAll(producer, consumer);

आउटपुट:

1 2 3 4 5 6 7 8 9 10


अद्यतन: C # 8 के रिलीज के साथ, अतुल्यकालिक गणना एक अंतर्निहित भाषा सुविधा बन गई है। आवश्यक कक्षाएं ( IAsyncEnumerable, IAsyncEnumerator) .NET कोर 3.0 में एम्बेडेड हैं, और .NET फ्रेमवर्क 4.6.1+ ( Microsoft.Bcl.AsyncInterfaces ) के लिए एक पैकेज के रूप में पेश की जाती हैं ।

यहां एक वैकल्पिक GetConsumingEnumerableकार्यान्वयन है, जिसमें नए C # 8 सिंटैक्स की विशेषता है:

public async IAsyncEnumerable<T> GetConsumingEnumerable()
{
    lock (_queue) _consumersCount++;
    while (true)
    {
        lock (_queue)
        {
            if (_queue.Count == 0 && _isAddingCompleted) break;
        }
        await _semaphore.WaitAsync();
        bool hasItem;
        T item = default;
        lock (_queue)
        {
            hasItem = _queue.Count > 0;
            if (hasItem) item = _queue.Dequeue();
        }
        if (hasItem) yield return item;
    }
}

के सह-अस्तित्व नोट awaitऔर yieldएक ही विधि में।

उपयोग उदाहरण (C # 8):

var consumer = Task.Run(async () =>
{
    await foreach (var item in abc.GetConsumingEnumerable())
    {
        await Task.Delay(200);
        await Console.Out.WriteAsync(item + " ");
    }
});

से awaitपहले ध्यान दें foreach


1
एक विचार के रूप में, अब मुझे लगता है कि वर्ग का नाम AsyncBlockingCollectionनिरर्थक है। एक ही समय में कुछ अतुल्यकालिक और अवरुद्ध नहीं हो सकता है, क्योंकि ये दो अवधारणाएं सटीक विपरीत हैं!
थियोडोर जूलियास

0

अगर आपको थोड़ा हैक करने का मन नहीं है, तो आप इन एक्सटेंशन को आज़मा सकते हैं।

public static async Task AddAsync<TEntity>(
    this BlockingCollection<TEntity> Bc, TEntity item, CancellationToken abortCt)
{
    while (true)
    {
        try
        {
            if (Bc.TryAdd(item, 0, abortCt))
                return;
            else
                await Task.Delay(100, abortCt);
        }
        catch (Exception)
        {
            throw;
        }
    }
}

public static async Task<TEntity> TakeAsync<TEntity>(
    this BlockingCollection<TEntity> Bc, CancellationToken abortCt)
{
    while (true)
    {
        try
        {
            TEntity item;

            if (Bc.TryTake(out item, 0, abortCt))
                return item;
            else
                await Task.Delay(100, abortCt);
        }
        catch (Exception)
        {
            throw;
        }
    }
}

तो आप इसे बनाने के लिए एक कृत्रिम देरी लाते हैं? यह अभी भी सही अवरुद्ध है?
नवफाल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.