如何在 .net core 3.0 中并行运行多个 BackgroundService?

12
如何并行运行多个IHostedServices?
我使用.Net Core 3.0中的WorkerService,并希望两个服务并行运行。目前第二个服务正在等待第一个服务完成。两个服务应无限期运行。
    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        return Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<ServiceA>();
                services.AddHostedService<ServiceB>();
            });
    }

一个服务看起来就像这样:

public class ServiceA : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        do
        {
            Console.WriteLine("Sample");
            await Task.Delay(5000, stoppingToken);
        } while (!stoppingToken.IsCancellationRequested);
    }
}

//编辑: 非常勉强,我会使用Task.Run(() => method());这样的方法。但当然,这种方式总是有效的:

public class ServiceA : BackgroundService
{
    public override Task StartAsync(CancellationToken cancellationToken)
    {
        Task.Factory.StartNew(() => ExecuteAsync(cancellationToken), cancellationToken);
        return Task.CompletedTask;
    }
}

如果我将此示例与您的代码进行比较,我会建议您将do / while放在单独的异步方法中,并在ExecuteAsync中等待它...但这只是一个猜测,我仍在努力理解发生了什么。但我认为值得一试。 - Fildor
1
如果您想要基于定时器的执行,那么可以参考此示例 - Fildor
1
@cSteusloff -> 有点晚了..但是..这个回答是否对另一个SO问题有帮助?=> https://dev59.com/6bzpa4cB1Zd3GeqPIEt7 - Pure.Krome
3个回答

9

我曾经问过自己一个类似的问题,并进行了一些搜索,但没有找到一个好的答案。 我通过在Task.Run中使用来自BackgroundService.ExecuteAsync()的取消令牌运行每个后台服务来解决这个问题。 我有两个和你一样的服务。

public class BackgroundService1: BackgroundService
{
    public BackgroundService1()
    {
    }
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Task.Run(async () =>
        {
            await DoWork(stoppingToken);
        }, stoppingToken);
        return Task.CompletedTask;
    }
}

//第二个服务与第一个服务很相似:

public class BackgroundService2: BackgroundService
{
    public BackgroundService2()
    {
    }
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Task.Run(async () =>
        {
            await DoWork(stoppingToken);
        }, stoppingToken);
        return Task.CompletedTask;
    }
}

并在 Program.cs 中注册它们。

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

那很恶心,但我喜欢!我认为它可以是IHostedService而不是BackgroundService。 - Kamil Budziewski
如果您有添加DbContext和存储库的示例,请分享一下。我能够重现您在那里所做的操作,并且它可以正常工作,但是一旦我添加数据库访问,就会出现各种服务生命周期错误;我无法确定在ConfigureServices方法中添加DbContext和存储库时要使用哪个服务生命周期。 - Tom Regan

4

我也遇到了同样的问题:不同频率执行不同工作的多个服务。

经过调查,BackgroundService似乎是为顺序执行而设计的(对于基于无限循环的服务来说,这是最糟糕的敌人)。

在得到了这个线程的提示后,我找到了适用于我的情况的解决方案,使用了Microsoft的计时器服务示例

基础TimerService实现了IHostedServiceIAsyncDisposable

  • StartAsync()DoWork()上启动计时器
  • DoWork()是您可以重写的主要工作过程。
  • StopAsync()优雅地停止计时器。
  • DisposeAsync()清理。

我通过派生多个具有不同执行频率的 TimerService 并使用services.AddHostedService<>();将它们添加到应用程序中来进行测试。

它们都在同一时间启动和运行,并在规定时间内完成它们的任务。

/!\由于使用了计时器事件,因此它不是基于Task的。我指出这一点,因为有一次我混淆以时间为基础的事件和任务时经历了非常困难的故障排除经验 /!\


0

这是一个有效的反馈,但似乎并没有解决他们试图解决有关并行性的核心问题。 - Jeremy Caney
2
它确实解决了并行性问题。您可以覆盖StartAsync并调用base.StartAsync,而不是在ExecuteAsync中手动创建任务,这将返回ExecuteAsync作为任务。 - DBK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接