等待异步事件从永无止境的任务

时间:2019-09-04 12:46:46

标签: c# events .net-core channel system.threading.channels

当前,我正在构建一个需要等待传入消息等待的Web服务器,但是我需要异步等待这些消息。这些消息是在另一个继续运行的任务中收到的。无尽的任务会推送事件,而另一个任务则需要等待这些事件之一也被接收。

如果需要,可以制作草图以更好地可视化问题

我已经看过TaskCompletionSource类,但是除非客户端断开连接,否则该任务将无法完成,因此无法正常工作。等待任务完全相同。

是否存在用于C#/。net核心的库或内置解决方案?

谢谢:)

2 个答案:

答案 0 :(得分:3)

.NET Core 2.1和System.Threading.Channels中提供了新的Channel<T> API,可让您异步使用队列:

Channel<int> channel = Channel.CreateUnbounded<int>();
...
ChannelReader<int> c = channel.Reader;
while (await c.WaitToReadAsync())
{
    if (await c.ReadAsync(out int item))
    {
         // process item...
    }
}

请参阅this博客文章,以获取简介和使用示例。

答案 1 :(得分:1)

频道

ASP.NET Core SignalR使用Channels to implement event streaming-发布者方法异步生成由另一种方法处理的事件。在SignalR的情况下,是消费者将每个新事件推送给客户端。

在代码中进行相同的操作很容易,但是请确保遵循SignalR文档中显示的模式-该通道是由工作人员自己创建的,并且永远不会暴露给调用者。这意味着通道状态没有任何歧义,因为只有工人才能关闭通道。

另一个重要的一点是,即使有例外,您必须在完成后关闭频道。否则,消费者将无限期封锁。

您还需要将CancellationToken传递给生产者,以便您可以在某个时候终止它。即使您不打算明确取消生产者,也需要一种方法来告诉它在应用程序终止时停止。

以下方法创建通道,并且确保即使发生异常也将其关闭。

ChannelReader<int> MyProducer(someparameters,CancellationToken token)
{
    var channel=Channel.CreateUnbounded<int>();
    var writer=channel.Writer;

    _ = Task.Run(async()=>{
            while(!token.IsCancellationRequested)
            {
                var i= ... //do something to produce a value
                await writer.WriteAsync(i,token);
            }
        },token)
        //IMPORTANT: Close the channel no matter what.
        .ContinueWith(t=>writer.Complete(t.Exception));                
    return channel.Reader;
}
如果生产者正常完成或通过令牌取消了工作任务,则

t.Exception将为null。没有必要使用ChannelReader.TryComplete,因为一次只有一个任务正在写给写者。

消费者仅需要该读者来消费事件:

async Task MyConsumer(ChannerReader<int> reader,CancellationToken token)
{
    while (await reader.WaitToReadAsync(cts.Token).ConfigureAwait(false))
    {
        while (reader.TryRead(out int item))
        {
           //Use that integer
        }
    }
}

可以与:

一起使用
var reader=MyProducer(...,cts.Token);
await MyConsumer(reader,cts.Token);

IAsyncEnumerable

我在这里作了一点欺骗,因为我从ChannelReader.ReadAllAsync方法中复制了循环代码,该方法返回了一个IAsyncEnumerable,它允许异步循环。在.NET Core 3.0和.NET Standard 2.1(但不仅如此)中,可以用以下方式代替使用者:

await foreach (var i from reader.ReadAllAsync(token))
{
     //Use that integer
}

Microsoft.Bcl.AsyncInterfaces软件包为以前的.NET版本添加了相同的接口