runtime: BackgroundService blocked the execution of whole host
Describe the bug
When I run the specific foreach cycle in BackgroundService-derived class, it blocks the whole host from starting. Even second hosted service doesn’t start. When I comment foreach cycle, everything works as expected.
To Reproduce
TargetFramework: netcoreapp2.1 Version: 2.1.12
Use following hosted service: NotificationRunner.cs And singleton: NotificationService.cs
Expected behavior
BackgroundService.ExecuteAsync should work in background without blocking even if it has blocking code. As you can see in NotificationService class, cancellation of enumerator is based on IApplicationLifetime.ApplicationStopping, but anyway it shouldn’t affect the host startup because BackgroundService is expected to run in background 😃
Screenshots
When foreach cycle exists
Then execution is blocked on this cycle

But when foreach cycle is commented
Then execution continues as expected
(But why twice?)
Additional context
.NET Core SDK (reflecting any global.json):
Version: 3.0.100-preview7-012821
Commit: 6348f1068a
Runtime Environment:
OS Name: Windows
OS Version: 10.0.17763
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.0.100-preview7-012821\
Host (useful for support):
Version: 3.0.0-preview7-27912-14
Commit: 4da6ee6450
.NET Core SDKs installed:
2.1.700 [C:\Program Files\dotnet\sdk]
2.1.701 [C:\Program Files\dotnet\sdk]
2.1.801 [C:\Program Files\dotnet\sdk]
2.2.300 [C:\Program Files\dotnet\sdk]
2.2.301 [C:\Program Files\dotnet\sdk]
2.2.401 [C:\Program Files\dotnet\sdk]
3.0.100-preview7-012821 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0-preview7.19365.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 43
- Comments: 54 (14 by maintainers)
Links to this issue
Commits related to this issue
- Call Task.Yield() in TokenCleanupService so they don't block the app execution https://github.com/dotnet/runtime/issues/36063 WaitHandle.WaitOne replaced with Task.Dealy — committed to gekiss/IdentityServer4.Contrib.MongoDB by gekiss 4 years ago
- TokenCleanupService blocked the execution of whole host (#38) * Fixes #24 added TokenCleanup hosted service * Configured token cleanup in host application. * Call Task.Yield() in TokenCleanupSe... — committed to diogodamiani/IdentityServer4.Contrib.MongoDB by gekiss 3 years ago
- Refactor Orders consumer Also, the calling thread was getting blocked until there was a message available in the topic, thus preventing the host from starting. More info below: https://github.com/dot... — committed to fredimachado/NCafe by fredimachado 2 years ago
Fixed by adding
await Task.Yield()at the top of methodSorry I never did this change but enough people have hit it now I think we should just do it. https://github.com/aspnet/Extensions/tree/davidfowl/background-service
Just ran into this. Fixed with await Task.Yield() at top of BackgroundService.ExecuteAsync. I think this is especially confusing because we don’t call ExecuteAsync ourselves, the framework does. We don’t even instantiate the background service ourselves (we’re using services.AddHostedService<<MyBackgroundService>>()). So the fact that the instantiation of the service and the call to ExecuteAsync happen synchronously as part of the ASP.NET app startup is not obvious. In fact it’s downright strange imho.
I think it’s reasonable to dispatch
ExecuteAsyncto the thread-pool inStartAsync. If the user wants to perform initialization that must complete before letting the app continue, they can overrideStartAsyncitself, right?It’s called BackgroundService. It seems odd that you can block the foreground in the default case. If we’re not happy with overriding
StartAsyncwe could add a newInitializeAsyncthat is called andawaited duringStartAsync.This would be a breaking change however, since existing services may depend on the initial synchronous work blocking the startup process.
Task.Runis a perfectly viable workaround. But I’d still like to see it baked intoBackgroundServicebecause it’s not currently a pit of success.Most developers don’t think about
asyncmethods as starting synchronously. There’s a number of SO questions that are variants of “I started with a background service template and it works fine until I try to remove the Task.Delay” or “it stops working as soon as I try to [synchronously] read from a queue of work”. For those of us with a good understanding of async and how the host starts background services, the problem is obvious; but that’s not most developers.This issue hasn’t been planned for 7.0 (see https://github.com/dotnet/runtime/issues/64015). Moving to Future.
I just ran into this and was about to leave feedback on the docs. Glad I checked here first. The Worker template works because it awaits a Task.Delay. Change that to a
Thread.Sleep, remove async keyword, and return a CompletedTask from ExecuteAsync and it can be easily reproduced. Execution is not returned to the caller so StartAsync never finishes and the host never finishes initialization so cancellationtoken does not work and any other HostedServices registered would never start.I have also encountered this issue and solved it in the same way as @vova-lantsov-dev did. However, my main concern is that this behavior is not straightforward, would be nice if this was more predictable or documented somewhere
For what it’s worth:
The argument that you can just override
StartAsyncto accomplish the same thing is basically true but there is a pretty notable drawback: Now, state that you used to be able to initialize and use locally inExecuteAsynchas to be promoted to mutable class state. This gets particularly ugly when NRTs are enabled.This:
Turns into this:
This seems like a significant regression in code clarity to me.
There’s also an argument to be made that, if the current behavior of
ExecuteAsyncis misleading, then so too is theStartAsyncoverride in the example above.The name
BackgroundServiceis definitely misleading given the current behavior. But I also think that the current behavior is actually useful - I rely on it in two of my projects for sane synchronous initialization ordering. So, I hope that if this change does go through, an alternative class will be provided that retains the current behavior.The
IHostedLifecycleServiceandServicesStartConcurrentlychanges in .NET 8.0 do not address this issue. The host (as currently written) still starts the services by callingStartAsyncdirectly. So a synchronous implementation ofStartAsyncwill still block host startup.Look at the implementation of
BackgroundService.StartAsyncis a synchronous (!!) method which returns either theTasktaken as result fromExecuteAsync, if said taskIsCompleted; and otherwise returns a pre-completedTask.CompletedTask. It will take responsbility to stop the execute task inStopAsyncby triggering the cancellation token, regardless.Because
StartAsyncis synchronous, the code will block until the actualExecuteAsyncimplementation, if it is an async method, does its firstawait. That firstawaitis where the asynchronous state machine compiler magic effectively kicks in and the async method returns itsTaskin a yet-to-be-completed state.I’m not a fan as it’s wasteful, but if that many people run into it it might be worth doing. I’d honestly rather teach people that these things aren’t running on dedicated threads so blocking them isn’t the way to go.
Async method executes synchronously till first
awaitis reached (it meansBackgroundService.ExecuteAsyncwon’t return tillawaitis called). So this problem is not related to ASP.NET Core, but to async compilation.I think this should make it in NET 8.0 as indicated here https://github.com/dotnet/runtime/issues/86511 We have gotten two new properties added that should resolve this.
https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.hostoptions.servicesstartconcurrently?view=dotnet-plat-ext-8.0
https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.hostoptions.servicesstopconcurrently?view=dotnet-plat-ext-8.0
Wow what a wierd behaviour from a “background service”
.NET 8 has been released, and unfortunately, Microsoft has turned a blind eye to this issue. For those who have encountered it, it would be wise not to use BackgroundService. Instead, you can create your implementation based on IHostedService or the new IHostedLifecycleService.
The situation is amusing - to start a synchronous service, you need to await something, for example, Task.Yield, resulting in the generation of a state machine and a loss of at least 0.05% performance at this stage. Alternatively, you can use await Task.Run and lose at least 0.10% simply because of the presence of a state machine to start the service elegantly with just one addition to the DI container.
I’ve run into this too. @eerhardt could we make this happen in 8.0? this could be a relatively simple fix compared to the time spent scratching their heads 😃
So, I tried the above code, and apparently the issue only happens on Mac, on both vscode and vs for Mac.
I’m having the same problem using an
IAsyncEnumerablein the background thread. The operation shouldn’t be blocking (it’s an async Enumerable after all) but @pawepaw seems to be right about this. For now, theTask.Yieldworkaround will do it for me.Facing same problem that background tasks is blocking main thread with
IAsyncEnumerable. That’s probably due toIAsyncEnumerableimplementation https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8#under-the-hood-of-async-iterators@HelloKitty
IHostedService.StartAsyncalways blocks the execution of the whole host. This is expected behaviour. Try to useBackgroundService.ExecuteAsyncfor non-blocking delayed tasks.Reopening this so we can look at it. I’m glad that you solved your problem but we should try to solve this so others don’t hit it.