I have 2 services inheriting from Microsoft.Extensions.Hosting.BackgroundService; if I register both of them with either AddSingleton or AddHostedService, only the first registered service seems to start.
services.AddSingleton<IHostedService, PageViewLogBackgroundService>();
services.AddSingleton<IHostedService, TracePoolingService>();
Both services are set up with the following line inside ExecuteAsync() as the first line:
_logger.LogDebug($"{GetType().Name} is starting.");
Output:
[21:02:53 DBG] PageViewLogBackgroundService is starting.
Using AspNetCore 2.1.2; unsure if this is fixed in 2.1.4
Can confirm, still an issue on 2.1.4
Okay, so my BackgroundServices were blocking the ExecuteAsync() - maybe it'd be better to run each background service on its own thread?
Of course, if you do not complete the tasks from StartAsync, then the application host cannot be sure that your services are actually started successfully. You should make sure that the tasks complete when your services are running and when the host can continue with the next host or with running the web application.
You are free to spawn your own threads as part of the StartAsync on which your background service will run. But it is not the job of the framework to make that decision for you. But even then, the StartAsync should complete as soon as the thread is ready.
That's fair, although in the case of BackgroundService, it is already a class made for persistent background services, isn't it? It'd be my assumption that when using it, it is a background service running on its own thread.
In fact, even the samples on the MSDN blog use blocking tasks for BackgroundService without any mention of the fact that this would completely block the WebHost as well. https://blogs.msdn.microsoft.com/cesardelatorre/2017/11/18/implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x/
This is from the official documentation of BackgroundService for ExecuteAsync:
This method is called when the IHostedService starts. The implementation should return a task that represents the lifetime of the long running operation(s) being performed.
In which case, it really should manage its own thread, since a long lifetime Task won't finish anytime soon.
To quote the description for BackgroundService.ExecuteAsync (emphasis mine):
This method is called when the
IHostedServicestarts. The implementation should return a task that represents the lifetime of the long running operation(s) being performed.
You should actually return a task here. So if your implementation is blocking, then the method will not return an asynchronous task. But if you implement your background service asynchronously, then the task is being returned automatically. This is also the case in the article you linked:
c#
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
CheckConfirmedGracePeriodOrders();
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
}
The CheckConfirmedGracePeriodOrders is expected to finish quickly (ideally it should be asynchronous as well) so the await will be reached quickly. This makes the method asynchronous and it will not block.
If you need to run on a separate thread, then you should spawn your own thread here.
Aaaaaah I see. My bad then.
I ran into this same issue. Here is a StackOverflow question I asked regarding this: basically, I am using Task.CompletedTask and it is blocking, while a Task.Delay(1) is not. Is returning a Task.CompletedTask harmful?
@Larry57 You need to return a task or yield the task as part of the main process loop to allow cooperative cancellation to run. It would be safer to wrap the pipeline in a separate Task.Run. The blocking you mention is actually the method never escaping the process loop as shutdown is never observed.
The simplest workaround is to add await Task.Yield; as line one of ExecuteAsync.
If everything actually returns synchronously then the method never yields and StartAsync does not complete.
Another visible side-effect of this problem is that if you have a background service in an aspnet project then you never see the "Application started" logging on the console. Indicating that the bootstrap process is blocked.
The simplest workaround is to add
await Task.Yield;as line one ofExecuteAsync.
await Task.Yield(); // Parenthesis were missing
Most helpful comment
The simplest workaround is to add
await Task.Yield;as line one ofExecuteAsync.If everything actually returns synchronously then the method never yields and
StartAsyncdoes not complete.Another visible side-effect of this problem is that if you have a background service in an aspnet project then you never see the "Application started" logging on the console. Indicating that the bootstrap process is blocked.