多个IHostedService实现

15

我正在尝试使用IHostedService创建后台服务。如果我只有一个后台服务,一切都运行良好。当我尝试创建多个IHostedService实现时,只有第一个被注册的实现会真正运行。

services.AddSingleton<IHostedService, HostedServiceOne>();
services.AddSingleton<IHostedService, HostedServiceTwo>();
在上述示例中,调用了HostedServiceOne上的StartAsync,但从未调用HostedServiceTwo上的StartAsync。如果我交换注册IHostedService的两个实现的顺序(将IHostedServiceTwo放在IHostedServiceOne之前),那么HostedServiceTwo上的StartAsync会被调用,但对于HostedServiceOne则永远不会被调用。
编辑:
我被引导到以下内容: 如何在Asp.Net Core 中注册相同接口的多个实现? 然而,这不适用于IHostedService。要使用建议的方法,我必须调用serviceProvider.GetServices<IService>();,但似乎IHostedService.StartAsync是在内部调用的。我甚至不确定在哪里调用它来触发IHostedService.StartAsync

提供的解决方案似乎需要调用serviceProvider.GetServices。我应该在哪里调用它以触发IHostedService.StartAsync?当使用单个IHostedService时,StartAsync方法似乎会在内部被调用。 - James
我明白了,那就不是重复的问题,稍后我会回答这个问题。 - Javier Capello
8个回答

16

我遇到了同样的问题。 在每个服务中必须返回Task.CompletedTask;

public class MyHostedService: IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        Task.Run(() => SomeInfinityProcess(cancellationToken));
        return Task.CompletedTask;
    }

    public void SomeInfinityProcess(CancellationToken cancellationToken)
    {
        for (; ; )
        {
            Thread.Sleep(1000);
            if (cancellationToken.IsCancellationRequested)
                break;
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

Startup.cs 是相同的:

    services.AddHostedService<MyHostedService>();
    services.AddHostedService<MyHostedService2>();
    ...

10

按照以下方式注册您的HostedService

// services.AddSingleton<IHostedService, HostedServiceOne>();
// services.AddSingleton<IHostedService, HostedServiceTwo>();

services.AddHostedService<HostedServiceOne>();
services.AddHostedService<HostedServiceTwo>();

[更新]:

请见 @nickvane 的评论:

这是因为第一个注册的服务在 StartAsync 方法上没有返回 Task,所以运行时会等待并不执行下一个已注册的 HostedService 实例的 StartAsync。

很可能第一个 StartAsync() 没有完成。


AddHostedService的实现只有一行代码:services.AddTransient<IHostedService, THostedService>(); - nickvane
@nickvane 您的意思是我们需要注册一个瞬态服务器而不是使用 AddHostedService 吗? - itminus
1
我的观点是,除了瞬态和单例范围之外,代码是相同的,范围的更改不是问题的原因。您可以将其注册为瞬态或单例,但这并不能解决问题。这是因为第一个注册的服务在StartAsync方法上没有返回任务,因此运行时正在等待,并且不会执行下一个已注册的hostedservice实例的StartAsync方法。 - nickvane
@nickvane 听起来很有前途。我跟随 @Harry 的答案中的链接,发现 它在等待第一个启动完成后再尝试启动下一个。我没有尝试过让第一个 StartAsync 不返回一个 Task。我认为如果我返回其他东西,它就不会编译。但是,如果第一个 StartAsync 没有完成,这是有道理的。虽然 OP 没有向我们展示代码,但我猜你是对的,应该是那种情况。 - itminus

4

保持异步!如这里所讨论的...

protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{            
    while (!stoppingToken.IsCancellationRequested)
    {
        // Do something nice here
        await Task.Delay(2000);
    }
}   

1
我运行了 Thread.Sleep(1000),然后摸着头想为什么我的另一个工作程序没有执行... - Mario Tacke
这应该是答案,但@Harry没有提到您的托管服务类必须继承BackgroundService。-它可以在Microsoft.Extensions.Hosting中找到。 - bmiller

1
我遇到了同样的问题,多个IHostedService的实现不会被调用,只有第一个。请检查您是否在IWebHostBuilder.ConfigureServices()方法中使用了TryAdd方法而不是Add方法。前者不允许接口的重复实现,后者允许。这对我有用。所以请使用以下代码:
webHost = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(x => x.Add(serviceCollection))

而不是这个:

webHost = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(x => x.TryAdd(serviceCollection))

1
这似乎可以通过装饰IHostedService来解决,尽管 .Net Core 的默认IoC容器不支持注册装饰器,但有一个简单的解决方法。
您可以像这样为IHostedService创建一个装饰器:
public abstract class MyHostedServiceDecorator : IHostedService
{
    private readonly MyHostedServiceDecorator _next;

    protected MyHostedServiceDecorator(MyHostedServiceDecorator next)
    {
        _next = next;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await StartAsyncInternal(cancellationToken);

        if (_next != null)
        {
            await _next.StartAsync(cancellationToken);
        }
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await StopAsyncInternal(cancellationToken);

        if (_next != null)
        {
            await _next.StopAsync(cancellationToken);
        }
    }

    protected abstract Task StartAsyncInternal(CancellationToken token);

    protected abstract Task StopAsyncInternal(CancellationToken token);
}

创建您需要的所有装饰器,例如:

。该内容涉及编程,并保留HTML格式,不做解释。

public class HostedServiceOneDecorator : MyHostedServiceDecorator
{
    public HostedServiceOneDecorator(MyHostedServiceDecorator next) : base(next)
    {
    }

    protected override async Task StartAsyncInternal(CancellationToken token)
    {
        Console.Write("This is my decorated start async!");
    }

    protected override async Task StopAsyncInternal(CancellationToken token)
    {
        Console.Write("This is my decorated stop async!");
    }
}

在您注册的托管服务中调用装饰器,如下所示:
public class MyHostedService : IHostedService
{
    private readonly MyHostedServiceDecorator
        _decorator;

    public MyHostedService(MyHostedServiceDecorator decorator)
    {
        _decorator = decorator;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // base StartAsync logic ...
        await _decorator.StartAsync(cancellationToken);
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        // base StopAsync logic ...
        await _decorator.StopAsync(cancellationToken);
    }
}

最后,您通过将下一个装饰器传递给上一个的构造函数来注册服务及其装饰器。
services.AddSingleton<IHostedService, MyHostedService>();

services.AddSingleton<MyHostedServiceDecorator>(
    new HostedServiceOneDecorator(new HostedServiceTwoDecorator(/*etc*/)));

所有装饰器将按照链式方式调用,直到没有下一个为止!

0

将IHostedService切换为BackgroundService。将StartAsync中的任何阻塞代码移动到ExecuteAsync中,并在StartAsync的末尾返回base.StartAsync(cancellationToken)。


0

简短版:将 IHostedService 切换为 BackgroundService

您必须在 StartAsync 中结束执行。如果您有一个长时间运行的任务,最好切换到 BackgroundService(或打开另一个线程)。BackgroundService 只有一个 ExecuteAsync 方法(不需要结束)。与 IHostedServiceStartAsync 方法相比(应该结束,否则其他服务将无法执行)。

public class GracePeriodManagerService : BackgroundService
{
    private readonly ILogger<GracePeriodManagerService> _logger;
    private readonly OrderingBackgroundSettings _settings;
    private readonly IEventBus _eventBus;

    public GracePeriodManagerService(IOptions<OrderingBackgroundSettings> settings,
                                     IEventBus eventBus,
                                     ILogger<GracePeriodManagerService> logger)
    {
        // Constructor's parameters validations...
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogDebug($"GracePeriodManagerService is starting.");

        stoppingToken.Register(() =>
            _logger.LogDebug($" GracePeriod background task is stopping."));

        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogDebug($"GracePeriod task doing background work.");

            // This eShopOnContainers method is querying a database table
            // and publishing events into the Event Bus (RabbitMQ / ServiceBus)
            CheckConfirmedGracePeriodOrders();

            await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
        }

        _logger.LogDebug($"GracePeriod background task is stopping.");
    }
}

注册方式相同:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    //Other DI registrations;

    // Register Hosted Services
    services.AddHostedService<GracePeriodManagerService>();
    services.AddHostedService<MyHostedServiceB>();
    services.AddHostedService<MyHostedServiceC>();
    //...
}

0

我遇到了一个稍微不同的问题,但是解决方案可能也适用于这里。我需要将服务实现注册为单例,并作为IHostedService运行。基于Javier Capello的解决方案,我想出了以下代码:

public class HostedServiceDecorator<T> : IHostedService where T : IHostedService
{
    private readonly IHostedService _next;

    public HostedServiceDecorator(T target)
    {
        _next = target;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        if (_next != null)
        {
            await _next.StartAsync(cancellationToken);
        }
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        if (_next != null)
        {
            await _next.StopAsync(cancellationToken);
        }
    }
}

然后我就可以做我需要的事情了:

        services.AddSingleton<INetworkStatusService, NetworkStatusService>();
        services.AddHostedService<HostedServiceDecorator<INetworkStatusService>>();

回答这个问题,可以这样做:

        services.AddTransient<NetworkStatusService>();
        services.AddHostedService<HostedServiceDecorator<NetworkStatusService>>();

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