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.
-
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– James2018年10月09日 02:46:06 +00:00Commented Oct 9, 2018 at 2:46
-
I see, it's not a duplicate then, I'll answer this in a bitJavier Capello– Javier Capello2018年10月09日 02:48:47 +00:00Commented Oct 9, 2018 at 2:48
8 Answers 8
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>();
...
Comments
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
4 Comments
services.AddTransient<IHostedService, THostedService>();AddHostedService ?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.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);
}
}
2 Comments
Thread.Sleep(1000) and was scratching my head why my other worker did not execute...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!
Comments
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))
Comments
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>>();
Comments
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>();
//...
}
Comments
Switch IHostedService to BackgroundService. Move any blocking code in StartAsync to ExecuteAsync and at the end of StartAsync return base.StartAsync(cancellationToken).