James
James

Reputation: 553

Multiple Implementations of IHostedService

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.

Upvotes: 15

Views: 20783

Answers (8)

DBK
DBK

Reputation: 403

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

Upvotes: 0

Fritz
Fritz

Reputation: 943

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

Upvotes: 0

itminus
itminus

Reputation: 25360

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

Upvotes: 11

netaholic
netaholic

Reputation: 1385

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

Upvotes: 0

Harry
Harry

Reputation: 41

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

Upvotes: 4

Telepnev Andrey
Telepnev Andrey

Reputation: 329

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>();
    ...

Upvotes: 17

MartinJ
MartinJ

Reputation: 11

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

Upvotes: 1

Javier Capello
Javier Capello

Reputation: 744

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!

Upvotes: 1

Related Questions