BackgroundService 会阻止应用程序启动,直到作业完成

问题描述 投票:0回答:1

我有两个后台服务,一个用于将数据写入通道,一个用于从该通道读取数据。 出于演示目的,我创建了如下所示的通道和后台服务:

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
有助于程序正常工作。这是框架中的错误吗?

c# async-await background-service system.threading.channels
1个回答
3
投票

程序启动时,必须等待Writer循环完成,然后Reader才会运行并读取通道中的所有数据,然后启动浏览器。所有这些过程都是同步的。

编写器是完全同步的,我怀疑读取器和应用程序同时启动,读取器可能在浏览器启动之前完成,因为它更快。

但是,作者肯定是同步的。有几种行为共同导致了这种情况:

  1. BackgroundService.ExecuteAsync
    由执行主机启动代码的线程直接调用,而不是在单独的线程池线程上调用。这意味着主机启动将不会继续,直到
    ExecuteAsync
    返回
    Task
  2. 通道无界。这意味着
    WriteAsync
    实际上并不是异步运行的。它将异步等待直到空间可用,然后添加一个项目,但由于总是有可用空间,因此“异步等待空间可用”实际上永远不会发生。

await Task.Delay
代码通过使
ExecuteAsync
立即返回未完成的任务来打破这一点,从而允许主机启动继续。

在我的博客上,我建议将 ExecuteAsync

 包装在 
Task.Run
 中,这与 
await Task.Delay
 具有类似的效果(但没有竞争条件)。

.NET 团队正在

考虑更改此行为。从技术上讲,这不是一个错误,但它让足够多的人感到惊讶,他们正在考虑更改它。

© www.soinside.com 2019 - 2024. All rights reserved.