我有两个后台服务,一个用于将数据写入通道,一个用于从该通道读取数据。 出于演示目的,我创建了如下所示的通道和后台服务:
public class DemoChannel
{
public Channel<int> Channel { get; }
public DemoChannel()
{
Channel = System.Threading.Channels.Channel.CreateUnbounded<int>(
new UnboundedChannelOptions
{
SingleWriter = false,
SingleReader = false,
AllowSynchronousContinuations = false,
});
}
}
public class Reader : BackgroundService
{
private readonly DemoChannel _channel;
public Reader(DemoChannel channel)
{
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (await _channel.Channel.Reader.WaitToReadAsync(stoppingToken))
{
if (_channel.Channel.Reader.TryRead(out var series))
{
Console.WriteLine("Read: " + series);
}
}
}
}
public class Writer : BackgroundService
{
private readonly DemoChannel _channel;
public Writer(DemoChannel channel)
{
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//await Task.Delay(1, stoppingToken);
for (var i = 0; i < 100000; i++)
{
Console.WriteLine("------Write: " + i);
await _channel.Channel.Writer.WriteAsync(i, stoppingToken);
}
}
}
注册服务:
builder.Services.AddSingleton<DemoChannel>();
builder.Services.AddHostedService<Writer>();
builder.Services.AddHostedService<Reader>();
注意我注释了一段代码:
//await Task.Delay(1, stoppingToken);
程序启动时,必须等待
Writer
完成循环,Reader
才会运行并读取通道中的所有数据,然后启动浏览器。所有这些过程都是同步的。
如果我在 Writer 中取消注释代码:
//await Task.Delay(1, stoppingToken);
,程序的行为完全符合我的预期,即 Writer
和 Reader
同时启动,并且浏览器立即打开,无需等待。这让我很困惑,我不明白为什么添加 Delay
有助于程序正常工作。这是框架中的错误吗?
程序启动时,必须等待Writer循环完成,然后Reader才会运行并读取通道中的所有数据,然后启动浏览器。所有这些过程都是同步的。
编写器是完全同步的,我怀疑读取器和应用程序同时启动,读取器可能在浏览器启动之前完成,因为它更快。
但是,作者肯定是同步的。有几种行为共同导致了这种情况:
BackgroundService.ExecuteAsync
由执行主机启动代码的线程直接调用,而不是在单独的线程池线程上调用。这意味着主机启动将不会继续,直到 ExecuteAsync
返回 Task
。WriteAsync
实际上并不是异步运行的。它将异步等待直到空间可用,然后添加一个项目,但由于总是有可用空间,因此“异步等待空间可用”实际上永远不会发生。await Task.Delay
代码通过使ExecuteAsync
立即返回未完成的任务来打破这一点,从而允许主机启动继续。
在我的博客上,我建议将 ExecuteAsync
包装在
Task.Run
中,这与 await Task.Delay
具有类似的效果(但没有竞争条件)。.NET 团队正在