是否有异步BlockingCollection <t>?</t>

时间:2014-01-20 02:30:50

标签: c# .net collections task-parallel-library async-await

我想await异步BlockingCollection<T>.Take()的结果,所以我不会阻止该线程。寻找这样的事情:

var item = await blockingCollection.TakeAsync();

我知道我可以这样做:

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

但是有点杀死了整个想法,因为另一个(ThreadPool)线程被阻止了。

还有其他选择吗?

4 个答案:

答案 0 :(得分:62)

我知道有四种选择。

第一个是Channels,它提供了一个支持异步ReadWrite操作的线程安全队列。频道经过高度优化,如果达到阈值,可选择支持丢弃某些项目。

下一个是来自TPL DataflowBufferBlock<T>。如果您只有一位消费者,则可以使用OutputAvailableAsyncReceiveAsync,或者只将其与ActionBlock<T>相关联。有关详细信息,请see my blog

最后两个是我创建的类型,可在我的AsyncEx library中找到。

AsyncCollection<T>async近似等同于BlockingCollection<T>,能够包装并发生产者/消费者集合,例如ConcurrentQueue<T>ConcurrentBag<T>。您可以使用TakeAsync异步使用集合中的项目。有关详细信息,请see my blog

AsyncProducerConsumerQueue<T>是一个更易于移植的async兼容的生产者/消费者队列。您可以使用DequeueAsync异步使用队列中的项目。有关详细信息,请see my blog

这三种替代方案允许同步和异步放置和接受。

答案 1 :(得分:12)

......或者你可以这样做:

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

简单,功能齐全的异步FIFO队列。

  

注意:之前在.NET 4.5中添加了SemaphoreSlim.WaitAsync,这并不是那么简单。

答案 2 :(得分:2)

如果您不介意任何改动,可以尝试这些扩展。

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

答案 3 :(得分:0)

这是BlockingCollection的非常基本的实现,它支持等待,但缺少许多功能。它使用了著名的AsyncEnumerable类,该类在C#8发布(引入异步流)之后将被淘汰,但仅适用于.NET Core 3.0。 .NET Framework无法获得此升级,因此AsyncEnumerable仍将部分有用。

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