15

I'm trying to create background services using IHostedService. Everything works fine if I only have ONE background service. When I try to create more than one implementation of IHostedService only the one that was registered first actually runs.

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

In the above sample StartAsync on HostedServiceOne gets called but StartAsync on HostedServiceTwo never gets called. If I swap the order of registering the two implementations of IHostedService (put IHostedServiceTwo before IHostedServiceOne) then StartAsync on HostedServiceTwo gets called but never for HostedServiceOne.

EDIT:

I was directed to the following:

How to register multiple implementations of the same interface in Asp.Net Core?

However this isn't for IHostedService. To use the suggested approach I would have to make a call to serviceProvider.GetServices<IService>(); but it seems that IHostedService.StartAsync seems to be called internally. I'm not even sure where I would call that to trigger IHostedService.StartAsync.

itminus
  • 23,772
  • 2
  • 53
  • 88
James
  • 523
  • 1
  • 4
  • 20
  • The solution provided seems to require calling serviceProvider.GetServices. Where would I call this to trigger IHostedService.StartAsync. When using a single IHostedService the StartAsync method seems to be called internally. – James Oct 09 '18 at 02:46
  • I see, it's not a duplicate then, I'll answer this in a bit – Javier Capello Oct 09 '18 at 02:48

8 Answers8

16

I had the same problem. It was necessary to return Task.CompletedTask in each services;

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 is same:

    services.AddHostedService<MyHostedService>();
    services.AddHostedService<MyHostedService2>();
    ...
tlp
  • 169
  • 1
  • 8
10

Register your HostedService as below :

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

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

[Update]:

See comments below by @nickvane :

It's because the first service registered is not returning a Task on the StartAsync method so the runtime is waiting and doesn't execute the StartAsync of the next registered hostedservice instance

It's likely the first StartAsync() didn't finish

itminus
  • 23,772
  • 2
  • 53
  • 88
  • The implementation of AddHostedService is only 1 line: `services.AddTransient();` – nickvane Jun 19 '19 at 19:47
  • @nickvane So do you mean we need register a transient server instead of using `AddHostedService` ? – itminus Jun 20 '19 at 00:32
  • 1
    My point was that the code is the same except for transient vs singleton scope and that the scope change is not the cause of the issue. You can either register it as transient or singleton, but it won't fix the issue. It's because the first service registered is not returning a Task on the StartAsync method so the runtime is waiting and doesn't execute the StartAsync of the next registered hostedservice instance. – nickvane Jun 22 '19 at 08:10
  • @nickvane Sounds promising. I follow the link within @Harry 's answer and find [It's waiting for the first one to finish starting before it tries to start the next one](https://github.com/aspnet/Hosting/issues/1560#issuecomment-427398704). I didn't give a try to make the first `StartAsync` not return a Task. I thought it won't compile if I return something else. But it make sense if the first StartAsync didn't finish. Although the OP didn't show us the code, I guess you're right and it should be that case. – itminus Jun 23 '19 at 13:57
4

Stay async! as discussed here...

https://github.com/aspnet/Hosting/issues/1560

protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{            
    while (!stoppingToken.IsCancellationRequested)
    {
        // Do something nice here
        await Task.Delay(2000);
    }
}   
Harry
  • 41
  • 2
  • 1
    I ran `Thread.Sleep(1000)` and was scratching my head why my other worker did not execute... – Mario Tacke May 15 '19 at 18:31
  • This should be the answer, but @Harry failed to mention that your hosted service class must inherit BackgroundService. -Which is found in Microsoft.Extensions.Hosting – bmiller Sep 21 '19 at 15:52
1

This seems that it can be solved by decorating the IHostedService, although .Net Core's default IoC container does not support registering decorators, there's an easy workaround for that.

You can create a decorator for the IHostedService like this:

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);
}

Create as many decorators you need like:

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!");
    }
}

In your registered hosted service call the decorator like this:

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);
    }
}

And finally you register service and its decorators by passing the next decorator in the previous' constructor.

services.AddSingleton<IHostedService, MyHostedService>();

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

All the decorators will be called in a chain-like fashion until there is no next!

Javier Capello
  • 745
  • 4
  • 13
1

I had the same issue, where multiple implementations of IHostedService would not be called, only the first one. Check if you happen to be using the TryAdd method in the IWebHostBuilder.ConfigureServices() method instead of the Add method. The first on does not allow duplicate implementations of interfaces, the second one does. This fixed it for me.

So use this:

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

Instead of this:

webHost = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(x => x.TryAdd(serviceCollection))
MartinJ
  • 11
  • 2
0

I encountered a slightly different problem, however the solution for it might apply here as well. I needed to register service implementation as a singleton and to have it running as IHostedService. Based on Javier Capello solution I came up with something like this:

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);
        }
    }
}

And then I could do what I needed:

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

And to answer the question, one might do this:

        services.AddTransient<NetworkStatusService>();
        services.AddHostedService<HostedServiceDecorator<NetworkStatusService>>();
netaholic
  • 1,345
  • 10
  • 22
0

SHORT: Switch IHostedService to BackgroundService

You have to end execution in StartAsync. If you have a long lasting task, better switch to a BackgroundService (or open another Thread). The BackgroundService only has a ExecuteAsync method (which don't needs to end). Compared to IHostedServices StartAsync method (which should end, or the other services will not be executed).

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.");
    }
}

registration works the same:

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

    // Register Hosted Services
    services.AddHostedService<GracePeriodManagerService>();
    services.AddHostedService<MyHostedServiceB>();
    services.AddHostedService<MyHostedServiceC>();
    //...
}
Fritz
  • 831
  • 7
  • 23
0

Switch IHostedService to BackgroundService. Move any blocking code in StartAsync to ExecuteAsync and at the end of StartAsync return base.StartAsync(cancellationToken).

DBK
  • 403
  • 4
  • 13