我想await
基于BlockingCollection<T>.Take()
异步的结果,所以我不阻塞线程。寻找这样的事情:
var item = await blockingCollection.TakeAsync();
我知道我可以这样做:
var item = await Task.Run(() => blockingCollection.Take());
但这有点使整个想法付诸东流,因为(的ThreadPool
)另一个线程被阻塞了。
还有其他选择吗?
我想await
基于BlockingCollection<T>.Take()
异步的结果,所以我不阻塞线程。寻找这样的事情:
var item = await blockingCollection.TakeAsync();
我知道我可以这样做:
var item = await Task.Run(() => blockingCollection.Take());
但这有点使整个想法付诸东流,因为(的ThreadPool
)另一个线程被阻塞了。
还有其他选择吗?
Task
基于库导出的API。例如,可以从ASP.NET使用它。有问题的代码在那里无法很好地扩展。
ConfigureAwait
在Run()
?之后使用仍然会是一个问题吗?[ed。没关系,我明白您现在在说什么]
Answers:
我知道有四种选择。
第一个是Channels,它提供了一个支持异步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
用来异步使用队列中的项目。有关更多信息,请参阅我的博客。
这些选择的最后三个允许同步和异步放置。
AsyncCollection.TryTakeAsync
,但在下载的Nito.AsyncEx.Coordination.dll 5.0.0.0
(最新版本)中找不到。程序包中不存在所引用的Nito.AsyncEx.Concurrent.dll。我想念什么?
while ((result = await collection.TryTakeAsync()).Success) { }
。为什么将其删除?
...或者您可以执行以下操作:
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队列。
注意:
SemaphoreSlim.WaitAsync
.NET 4.5之前已添加,但这并不是那么简单。
for
什么用?如果释放了信号量,则队列中至少有一项要出队,不是吗?
TryDequeue
are,带有值返回,或者根本不返回。从技术上讲,如果您有多个读取器,则同一读取器可以消耗两个(或多个)项目,然后其他任何读取器完全唤醒。成功WaitAsync
只是一个信号,表明队列中可能有要消耗的物品,这不是保证。
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
被破坏。是不是
这是BlockingCollection
支持等待的的非常基本的实现,但缺少许多功能。它使用该AsyncEnumerable
库,从而使8.0之前的C#版本可以进行异步枚举。
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
。
AsyncBlockingCollection
是荒谬的。某些东西不能同时异步和阻塞,因为这两个概念是完全相反的!
如果您不介意一点技巧,可以尝试这些扩展。
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;
}
}
}
await Task.Run(() => blockingCollection.Take())
该任务将在其他线程上执行并且您的UI线程不会被阻塞,那不是重点吗?