Multiple Workers in .net core worker services?

The reason why "AddHostedService" not accept same class registered twice:

I checked the github source of "AddHostedService" and found that it is implemented as follows:

services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());

According to Microsoft documentation of "TryAddEnumerable" method, the service is only added if the collection contains no other registration for the same service and implementation type. This is the reason why I cannot run two copies of "Worker". (The "TryAddEnumerable" is used intentionally to avoid multiple instance created for same service. ref: https://github.com/aspnet/Extensions/issues/1078)


Solution:

So I can added two workers by the using "AddSingleton" directly...

services.AddSingleton<IHostedService, Worker>();
services.AddSingleton<IHostedService, Worker>();

It works.


Passing parameter to service constructor:

Then, I modified the Worker class constructor by adding a second parameter for loop interval. Finally, I use implementation factory in registering service as follow

services.AddSingleton<IHostedService>(sp => new Worker(sp.GetService<ILogger<Worker>>(), 1000));
services.AddSingleton<IHostedService>(sp => new Worker(sp.GetService<ILogger<Worker>>(), 2000));

Hi I am also looking for solution to run multiple instances of background service

Thanks to Raymond Wong, the solution worked perfectly.

Just want to extend it further to make it configurable using AppSettings

"AppSettings":{
   "MaxInstances":2
}

Then when registering background service

var limit = appSettings.MaxInstances;
for (int i = 0; i < limit ; i++)
{
   services.AddSingleton<IHostedService, Worker>();
}

So we can control the no of instances from azure configuration.


You can have a "parent" worker that launches the "real" workers like this...

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                var workers = new List<Task>();
                foreach(var delay in _config.LoopIntervals)
                    workers.Add(DoRealWork(delay, stoppingToken));

                await Task.WhenAll(workers.ToArray());
            }
        }

Then...

        private async Task DoRealWork(int delay, CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("worker {delay} checking in at {time}", delay, DateTimeOffset.Now);
                await Task.Delay(delay, stoppingToken);
            }
        }

_config gets populated from appSettings.json and passed in to the constructor of the Worker like this...

var cfg = hostContext.Configuration.GetSection(nameof(WorkerConfig)).Get<WorkerConfig>();
services.AddSingleton(cfg);
services.AddHostedService<Worker>();

and the appSettings...

{
  "WorkerConfig": {
    "LoopIntervals": [ 1000, 2000, 3000 ]
  }
}

Tags:

.Net Core