Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why a blocking call inside BackgroundService.ExecuteAsync is not blocking the main thread?

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?

like image 288
underthevoid Avatar asked Sep 11 '25 14:09

underthevoid


1 Answers

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.

like image 131
Oliver Avatar answered Sep 13 '25 04:09

Oliver