Multiple Workers in .net core worker services?

Viewed 10716

The dot net core 3.0 worker services template shown as follow:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices(services =>
            {
                services.AddHostedService<Worker>();
            });
}

The "Worker" class is derived from BackgroundService. It loops to write log to console every 1000 ms.

My questions:

Can I run multiple "Worker"s simultaneously? (I know I can create another class "Worker2". But can I run two copies of same class "Worker"?)

If yes, how I can configure two "Worker" with different configuration or parameters, say, two Workers with different looping intervals? (Because instance of "Worker" class is created by DI framework. I don't know how I can pass different config/parameters to two different instance of "Worker")

3 Answers

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 ]
  }
}

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.

Related