I'm implementing a Microsoft.Extensions.Hosting.BackgroundService
in a Asp.Net Core Web API, which has a blocking call inside ExecuteAsync
, but surprisingly (to me) it is actually not blocking my application and I'm wondering the reason.
Accordingly to the different versions of the source code of BackgroundService
I could find, the method Task ExecuteAsync
is called in a fire and forget fashion inside StartAync
. Source bellow.
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
From what I understand, the continuation of an await
call (ie. whatever is bellow it) will be executed in the same SynchronizationContext
that called this asynchronous method after the task returns. If this is true, then why isn't this continuation code (that has a blocking call) blocking my application?
To illustrate:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield();
Foo(); // Foo blocks its executing thread until an I/O operation completes.
}
As ExecutedAsync
is never awaited, the continuation of the method (ie. the Foo call) will be executing in the same sync context that fired the ExecuteAsync
task in the first place (which will be running in the main thread if I understood correctly).
I'm suspecting the Asp.Net runtime must have its own SynchronizationContext
that actually executes async continuations in different threads or something like that.
Can anyone shed some light here?
Two points come into play: SynchronizationContext and TaskScheduler.
In ASP.NET Core, the default SyncContext is null, allowing each task to run on whatever thread is available. As second the TaskScheduler comes into play. If it runs multiple tasks on the same thread one can block all the other tasks.
But the tasks running as background service are marked as LongRunning
to the task scheduler and the task scheduler gives each long running task its own thread. Thus your background service can't block the asp core engine by a blocking thread call.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With