2

As the title states, when I register multiple instances of IHostedService, it calls StartAsync twice on the first instance, but not the second, but it does call both constructors.

Program.cs

services.AddSingleton<IHostedService, ProductService>(provider => (ProductService)provider.GetService<IProductService>()!);
services.AddSingleton<IProductService, ProductService>();
services.AddSingleton<IHostedService, ProductService>(provider => (ProductService)provider.GetService<IProductService>()!);
services.AddSingleton<IProductService, ProductService>();

ProductService.cs

public class ProductService : IProductService, IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken) { }
    public async Task StopAsync(CancellationToken cancellationToken) { }
}

How can I solve this? I need multiple instances of ProductService (name changed for simplicity).

Starfish
  • 3,344
  • 1
  • 19
  • 47
  • You have registered `IProductService` as a **Singleton**, then you retrieve this singleton instance and register it as `IHostedService`. Obviously, both hosted services implemented by the **same** `IProductService` instance, that's why `StartAsync` was called twice on this **Singleton** instance. – Artur Dec 02 '22 at 11:04
  • 1
    This question is not clear, in the program.cs you have same lines that repeated and at the beginning you said the problem is calling twice for StartAsync but at the end : ``I need multiple instances of ProductService`` – sa-es-ir Dec 04 '22 at 17:06
  • Indeed, I want two instances of `ProductService`, so I declare two of them. It calls the constructors of both 1 and 2, but calls the `StartAsync` on instance 1 twice, while the second instance's `StartAsync` is not called. I have learned now that a Singleton registers once, but resolves the latest registered one. The answer pfx provided added clarity to my train of thoughts. I thought IHostedService was supposed to be a singleton now. I was not aware I could still register it as transient. – Starfish Dec 05 '22 at 11:55

3 Answers3

1

To spin up 2 hosted services of the same type, it suffices to register the same service 2 times with a transient lifetime scope.

Each will have a constructor call and a StartAsync call, which you can verify via the value of the Guid field in the example ProductService below.

services.AddTransient<IHostedService, ProductService>();
services.AddTransient<IHostedService, ProductService>();

This post explains that a hosted service used to be a long lived transient with the behavior of a singleton.


public class ProductService : IHostedService
{
    private readonly Guid _id = Guid.NewGuid();

    public ProductService( /* Any dependencies */ )
    { }

    public Task StartAsync(CancellationToken cancellationToken)
        => Task.CompletedTask;

    public Task StopAsync(CancellationToken cancellationToken)
        => Task.CompletedTask;
}
pfx
  • 20,323
  • 43
  • 37
  • 57
  • 1
    Thank you, I was not aware I could still register an `IHostedService` as Transient. I thought it changed to be a Singleton. But I learned since that just the `AddHostedService` registers an `IHostedService` as Singleton instead of Transient under the hood by default. – Starfish Dec 05 '22 at 11:58
0

Well, it's hard to say without your implementation of StartAsync but StartAsync method is intended to be non-blocking.

Schedule any work you need and then finish StartAsync.

StartAsync method of the second instance is waiting for the first one to finish starting before it tries to start the next one.

public Task StartAsync(CancellationToken cancellationToken)
{
    Task.Run(() => SomeWork(cancellationToken));
    return Task.CompletedTask;
}
user2250152
  • 14,658
  • 4
  • 33
  • 57
-1

You can use a factory to maintain n number of instances. i just added a sample

class ProductFactory : IProductFactory
{
    private readonly Dictionary<string, ProductService> _productService ;

    public void Register(string name, ProductService productService)
    {
        _productService[name] = productService;
    }

    public HttpClient Resolve(string name)
    {
        return _productService[name];
    }
}


var factory = new ProductFactory();
factory.Register("p1", new ProductService());
factory.Register("p2", new ProductService());
services.AddSingleton<IProductFactory>(factory);


public ProductController(IProductFactory factory)
{
    _productFactory = factory.Resolve("p1");
}

You can use factory not to start .

Avinash Reddy
  • 937
  • 8
  • 22