Net6 Worker进程中使用task.Run并行两个后台服务是否可以?

Is it ok to use task.Run to parallel two backgroundservice in a Net6 Worker process?

我正在使用 Net6 和 Worker 进程(而不是 asp.net)。我需要在同一个工作进程中并行拥有两个或更多后台服务运行。

我用 Task.Run(()=>...) 这样做,但我不知道这是否是一个好的做法,我想问问你,你对此有何看法。

所以我用两个 hostedServices 创建了一个新的 Worker 项目:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services
            .AddHostedService<Worker>()
            .AddHostedService<Worker2>();
        });

后台服务工作者实现:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

    for (int i = 0; i < 1000; i++)
    {
        if (stoppingToken.IsCancellationRequested) break;
        Console.WriteLine($"Worker1 - {i}");
    }
}

工人 2:

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

        for (int i = 0; i < 1000; i++)
        {
            if (stoppingToken.IsCancellationRequested) break;
            Console.WriteLine($"Worker2 - {i}");
        }
    }

我会得到结果

Worker1 - 1
Worker1 - 2
...
Worker1 - 999
Worker2 - 1
Worker2 - 2
...
Worker2 - 999

所以任务Worker2在Worker之后执行。

如果我在每个 ExecuteAsync 中使用 Task.Run:

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Run(() =>
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                for (int i = 0; i < 1000; i++)
                {
                    if (stoppingToken.IsCancellationRequested) break;
                    Console.WriteLine($"Worker1 - {i}");
                }

            });
        }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Run(() =>
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

            for (int i = 0; i < 1000; i++)
            {
                if (stoppingToken.IsCancellationRequested) break;
                Console.WriteLine($"Worker1 - {i}");
            }

        });
    }

输出为

Worker1 - 1
Worker2 - 1
Worker2 - 2
Worker1 - 1
...

这对我来说很好,但我想问你使用 Task.Run 并行化两个后台服务是否是一个好习惯。

我在某个地方读到过一些关于它的文章,这是避免 Task.Run 的好习惯。那我还应该用什么?

谢谢。

这取决于,但一般来说(如果 运行ning 进程有足够的资源来处理所有这些)可以在一个工作项目中 运行 多个托管服务。

虽然您的实现有一个大问题 - ExecuteAsync 方法实际上并不是 async 尽管被标记为这些方法并且会阻塞管道直到它们结束执行。快速解决方法是将 await Task.Yield() 添加到它们的 return 控件中(通过第二个示例中的 await Task.Run 实现类似的效果):

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
    await Task.Yield();
    // ....
}

注意the docs特别提到:

Avoid performing long, blocking initialization work in ExecuteAsync. The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

您可以检查多个 worker 的 ExecuteAsync 方法是否正在同步执行并且您的任务需要足够的 CPU 计算然后 Task.Run 可以用于并行化

此外,如果您不需要其结果 and/or 处理异常,则不必使方法异步并等待 CPU 任务完成(通过 await 运算符)。

I do that with Task.Run(()=>...) but I don't know if it is a good practice and I would ask to you what do you think about it.

Using Task.Run is normal because ExecuteAsync will otherwise block the host startup,正如我在我的博客中描述的那样。

它们是 planning to fix this behavior in .NET 7.0,可能是因为 BackgroundService 运行 ExecuteAsyncTask.Run 中,所以其他人不必这样做。