I was using AddSingleton and AddHostedService together to have a long running service (BackgroundService) in the backend while controllers accessing the same service to fetch data. Here are what I found:
- If both AddSingleton and AddHostedService were used, the BackgroundService would be initialized twice (not Singleton). The controller could only access the one created in AddSingleton.
debug output (constructor called twice, see the difference in the seconds)
BGService constructor service addGame:games count is 1 BGService constructor service addGame:games count is 1 ExecuteAsync 1 :games count is 1:[ "CONSTRUCTOR GAME AT 10/29/2021 10:37:25 AM(0)" ] ...... ExecuteAsync 7 :games count is 1:[ "CONSTRUCTOR GAME AT 10/29/2021 10:37:25 AM(0)" ] service addGame:games count is 2 After AddGame: [
"CONSTRUCTOR GAME AT 10/29/2021 10:37:24 AM(0)", "GAME 0(1)" ]
If only AddSingleton was used, it worked.
If only AddHostedService was used, the BackgroundService was running, but DI not working in controller (Exception).
**> fail:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware1 An unhandled exception has occurred while executing the request. System.InvalidOperationException: Unable to resolve service for type 'CoreServiceSignalRSample.service.IBGService' while attempting to activate 'CoreServiceSignalRSample.Controllers.WeatherForecastController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method(Closure , IServiceProvider , Object[] )**
My question is, what is the proper way to DI a BackgroundService and why AddHostedService is recommended?
The sample code can be found at https://github.com/huangpat/CoreServiceSignalRSample.
Startup.cs ConfigureServices
services.AddSingleton<IBGService>(new BGService());
services.AddHostedService<BGService>();
BGSercie.cs
private List<string> games;
public BGService()
{
Console.WriteLine("BGService constructor");
this.games = new List<string>();
this.addGame("Constructor Game at " DateTime.Now.ToString());
}
public async Task<bool> addGame(string fGame)
{
if (this.games.Count <= 20)
{
this.games.Add(fGame.ToUpper() "(" Convert.ToString(this.games.Count) ")");
Console.WriteLine("service addGame:games count is " Convert.ToString(this.games.Count));
return true;
}
else
{
return false;
}
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
int i = 0;
while (true)
{
i ;
if (games.Count == 0)
{
this.addGame("ExecuteAsync Game at " DateTime.Now.ToString());
}
JsonSerializer js = new JsonSerializer();
Console.WriteLine("ExecuteAsync {1} :games count is {0}:{2}", this.games.Count, i, JsonConvert.SerializeObject(this.games, Formatting.Indented));
await Task.Delay(1000 * 2 * 5);
}
//throw new NotImplementedException();
/
/ use thread pool to start each game in collection }
CodePudding user response:
Every registation will create it's own type descriptor with it's own resolution rules (i.e. by default it will use corresponding constructor). You can workaround it using registration with implementation factory:
services.AddSingleton<BGService>();
services.AddSingleton<IBGService>(provider => provider.GetRequiredService<BGService>());
services.AddHostedService<BGService>(provider => provider.GetRequiredService<BGService>())