为了提供生产者-消费者功能,使其能够一个接一个地排队和执行async方法,我正在尝试实现一个异步队列。在大型应用程序中使用它时,我注意到了主要的性能问题。
async Task Loop() {
while (Verify()) {
if (!_blockingCollection.TryTake(out var func, 1000, _token)) continue;
await func.Invoke();
}
}
AsyncQueue.Add:的实现
public void Add(Func<Task> func) {
_blockingCollection.Add(func);
}
来自任意线程的示例用法:
controller.OnEvent += (o, a) => _queue.Add(async (t) => await handle(a));
执行路径取决于应用程序的状态,包括
内部使用TaskCompletionSource返回结果的异步网络请求
IO操作
添加到列表中并使用任务等待的任务。WhenAll(…(
一种异步void方法,用于转换数组并等待网络请求
症状:应用程序逐渐变慢。
当我用func.Invoke().Wait()
替换await func.Invoke()
而不是正确地等待它时,性能会显著提高,而且速度不会减慢。
为什么?使用BlockingCollection
的异步队列是个坏主意吗?
什么是更好的选择?
为什么?
问题中没有足够的信息来提供答案。
正如其他人所指出的,循环目前存在消耗CPU的旋转问题
与此同时,我至少可以回答这一部分:
使用BlockingCollection的异步队列是个坏主意吗?
是。
什么是更好的替代方案?
使用异步兼容队列。例如,来自TPL数据流的Channels
或BufferBlock
/ActionBlock
。
使用通道的示例:
async Task Loop() {
await foreach (var func in channelReader.ReadAllAsync()) {
await func.Invoke();
}
}
或者如果你还没有使用.NET Core:
async Task Loop() {
while (await channelReader.WaitToReadAsync()) {
while (channelReader.TryRead(out var func)) {
await func.Invoke();
}
}
}