awaitable Task based queue

前端 未结 9 1108
礼貌的吻别
礼貌的吻别 2020-11-27 14:44

I\'m wondering if there exists an implementation/wrapper for ConcurrentQueue, similar to BlockingCollection where taking from the collection does not block, but is instead a

相关标签:
9条回答
  • 2020-11-27 15:44

    Simple approach with C# 8.0 IAsyncEnumerable and Dataflow library

    // Instatiate an async queue
    var queue = new AsyncQueue<int>();
    
    // Then, loop through the elements of queue.
    // This loop won't stop until it is canceled or broken out of
    // (for that, use queue.WithCancellation(..) or break;)
    await foreach(int i in queue) {
        // Writes a line as soon as some other Task calls queue.Enqueue(..)
        Console.WriteLine(i);
    }
    

    With an implementation of AsyncQueue as follows:

    public class AsyncQueue<T> : IAsyncEnumerable<T>
    {
        private readonly SemaphoreSlim _enumerationSemaphore = new SemaphoreSlim(1);
        private readonly BufferBlock<T> _bufferBlock = new BufferBlock<T>();
    
        public void Enqueue(T item) =>
            _bufferBlock.Post(item);
    
        public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default)
        {
            // We lock this so we only ever enumerate once at a time.
            // That way we ensure all items are returned in a continuous
            // fashion with no 'holes' in the data when two foreach compete.
            await _enumerationSemaphore.WaitAsync();
            try {
                // Return new elements until cancellationToken is triggered.
                while (true) {
                    // Make sure to throw on cancellation so the Task will transfer into a canceled state
                    token.ThrowIfCancellationRequested();
                    yield return await _bufferBlock.ReceiveAsync(token);
                }
            } finally {
                _enumerationSemaphore.Release();
            }
    
        }
    }
    
    0 讨论(0)
  • 2020-11-27 15:45

    Well 8 years later I hit this very question and was about to implement the MS AsyncQueue<T> class found in nuget package/namespace: Microsoft.VisualStudio.Threading

    Thanks to @Theodor Zoulias for mentioning this api may be outdated and the DataFlow lib would be a good alternative.

    So I edited my AsyncQueue<> implementation to use BufferBlock<>. Almost the same but works better.

    I use this in an AspNet Core background thread and it runs fully async.

    protected async Task MyRun()
    {
        BufferBlock<MyObj> queue = new BufferBlock<MyObj>();
        Task enqueueTask = StartDataIteration(queue);
    
        while (await queue.OutputAvailableAsync())
        {
            var myObj = queue.Receive();
            // do something with myObj
        }
    
    }
    
    public async Task StartDataIteration(BufferBlock<MyObj> queue)
    {
        var cursor = await RunQuery();
        while(await cursor.Next()) { 
            queue.Post(cursor.Current);
        }
        queue.Complete(); // <<< signals the consumer when queue.Count reaches 0
    }
    

    I found that using the queue.OutputAvailableAsync() fixed the issue that I had with AsyncQueue<> -- trying to determine when the queue was complete and not having to inspect the dequeue task.

    0 讨论(0)
  • 2020-11-27 15:48

    You could just use a BlockingCollection ( using the default ConcurrentQueue ) and wrap the call to Take in a Task so you can await it:

    var bc = new BlockingCollection<T>();
    
    T element = await Task.Run( () => bc.Take() );
    
    0 讨论(0)
提交回复
热议问题