Asynchronous System.Threading.Channels ReadAsync()方法正在阻止执行

Asynchronous System.Threading.Channels ReadAsync()方法正在阻止执行,asynchronous,.net-core,iasyncenumerable,Asynchronous,.net Core,Iasyncenumerable,概述 我正试图围绕IObserver接口编写一个IAsyncEnumerable包装器。起初我使用BufferBlock作为备份数据存储,但通过性能测试和研究,我发现它实际上是一种非常慢的类型,因此我决定让System.Threading.Channels.Channel类型开始。我在BufferBlock实现中遇到了与此类似的问题,但这次我不确定如何解决它 问题 如果我的IObserver.OnNext()方法尚未写入\u通道中,我的GetAsyncEnumerator()循环将被wait\u

概述

我正试图围绕
IObserver
接口编写一个
IAsyncEnumerable
包装器。起初我使用
BufferBlock
作为备份数据存储,但通过性能测试和研究,我发现它实际上是一种非常慢的类型,因此我决定让
System.Threading.Channels.Channel
类型开始。我在BufferBlock实现中遇到了与此类似的问题,但这次我不确定如何解决它

问题

如果我的
IObserver.OnNext()
方法尚未写入
\u通道
中,我的
GetAsyncEnumerator()
循环将被
wait\u通道.Reader.WaitToRead(令牌)
调用阻止。在这种情况下,在不阻塞程序执行的情况下,等待值可用以生成的正确方法是什么

实施

公共密封类observerAsyncEnumerableRapper:IAsyncEnumerable,IObserver,IDisposable
{
私人只读IDisposable _退订者;
私有只读通道_Channel=Channel.CreateUnbounded();
私人工厂生产完成;
公共ObserverAsyncEnumerableRapper(IObservable提供程序)
{
_unsubscriber=provider.Subscribe(此);
}
公共异步void OnNext(T值)
{
Verbose(“为通道添加值”);
wait_channel.Writer.WriteAsync(值);
}
公共无效OnError(异常错误)
{
_channel.Writer.Complete(错误);
}
未完成的公共无效()
{
_producerComplete=真;
}
公共异步IAsyncEnumerator GetAsyncEnumerator([EnumeratorCancellation]CancellationToken token=new CancellationToken())
{
Verbose(“启动异步迭代…”);
while(wait _channel.Reader.WaitToReadAsync(令牌)| |!_producerComplete)
{
Log.Logger.Verbose(“读取…”);
while(_channel.Reader.TryRead(out var项))
{
Log.Logger.Verbose(“屈服项”);
收益回报项目;
}
Verbose(“等待更多项目”);
}
Verbose(“迭代完成”);
_channel.Writer.Complete();
}
公共空间处置()
{
_channel.Writer.Complete();
_取消订阅程序?.Dispose();
}
}

附加上下文

这不重要,但在运行时,传递到构造函数中的
IObservable
实例是从对
Microsoft.Management.Infrastructure
API的异步调用返回的
CimAsyncResult
。它们利用了观察者设计模式,我正试图用新奇的异步枚举模式来包装它

编辑

通过记录到调试器输出进行更新,并按照一位评论员的建议,使我的
OnNext()
方法异步/wait。您可以看到它从未进入
while()
循环

在调用堆栈的后面,我通过GetAwaiter().GetResult()方法同步调用异步方法

我这样做是因为有一次我想从构造函数中获取数据。我将该实现更改为使用Task.Run()执行调用,现在迭代器在这两个实现中都能完美地运行

有比在异步代码上阻塞更好的解决方案,但最终您的用户体验仍然低于标准(我假设您的用户体验是一个UI应用程序,因为有一个
SynchronizationContext


如果使用异步枚举器加载要显示的数据,则更合适的解决方案是。如果异步枚举器用于其他用途,您可以在my.

微妙的注释中找到一些合适的替代模式:您永远不会等待
或检查来自
\u channel.Writer.WriteAsync(value)的响应
我按照您的建议更新了OnNext()方法,但没有效果。我应该提到,我不相信OnNext()在任何情况下都会异步执行,因为调用我的OnNext方法的ioberable也不是异步的,是第三方库类型。尽管如此,我认为这不应该影响我的统计员的行为。所以我明白了为什么我的程序会阻止执行。在调用堆栈的后面,我通过
GetAwaiter().GetResult()
方法同步调用异步方法。我发现,即使使用Reactive中的
ToAsyncEnumerable()
扩展方法,我也会得到相同的结果,即程序被阻塞。我这样做是因为有一次我想从构造函数中获取数据。我将该实现更改为使用Task.Run()执行调用,现在迭代器在这两种实现中都能完美地运行。我查看了Reactive中的
ToAsyncEnumerable
实现,它的构建量远远超过了我的实现。它使用
ConcurrentQueue
作为其数据源。我不知道性能概要与
频道相比如何,但我认为值得注意的是,《斯蒂芬·克利里回答了你的问题》有什么成就吗谢谢你的博客链接,我一定会看的。正如我在一篇评论中提到的,我最初实现的“GetAsyncEnumerator”在while循环中没有wait关键字。相反,它等待一个“SemaphoreSlim.WaitAsync()”,配置为一次只允许访问一个方法。当我将实现更改为我的问题时,在主线程上执行它会导致它停止,并且不允许我的生产者插入任何项目。