BackgroundService.StartAsync, should it complete during startup or block? - c#

I don't know if I'm missing some piece of this puzzle but as far as I'm used to working with services in general, any call to Start is supposed to always return so that you know your service has started.
I'm studying BackgroundService:
Background tasks with hosted services in ASP.NET Core
Implement background tasks in microservices with IHostedService and the BackgroundService class
In the examples on both, they're implementing ExecuteAsync (which is called by StartAsync) using a while loop meaning that ExecuteAsync (and by extension StartAsync) is only ever going to return when cancelled (like when the app shuts down).
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogDebug($"GracePeriod task doing background work.");
// This eShopOnContainers method is querying a database table
// and publishing events into the Event Bus (RabbitMQ / ServiceBus)
CheckConfirmedGracePeriodOrders();
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
}
This contradicts what I understand from services because how can you tell that the service has successfully started vs hanging on startup?
I'd really appreciate some clarity on this as I just can't seem to get my head around what's happening and I can't seem to find any further information specific to this.
Edit: Added call to ExecuteAsync from BackgroundService for illustration:
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;
}

Through the magic of async, even though ExecuteAsync appears to be an infinite loop, it really returns a Task to the caller. The await on the Delay is what allows control flow to leave the ExecuteAsync function, with the next iteration of the loop only running after that Task is resolved.
If you are not used to it, it can be easy to miss.

Related

What to return from ExecuteAsync for event-driven background tasks?

As part of my REST API I have a BackgroundService that subscribes to a message queue and waits for particular messages to be received and performs certain actions based on the type of message.
In ExecuteAsync I'm currently returning Task.CompletedTask since I don't have anything to 'await', however I'm thinking this is actually incorrect and could be potentially problematic although I'm not sure what exactly how or what problems could arise as a result.
Would it be acceptable to simply 'await' Task.Delay with an infinite delay? Something like:
await Task.Delay(Timeout.infinite, cancellationToken);
TLDR
Essentially it boils down to: Return Task.CompleteTask.
I'd even recommend just using IHostedService directly instead of BackgroundService because you aren't really gaining anything from what I can see.
IHostedService
The first thing to look into is IHostedService. This interface is what BackgroundService implements, and is what enables running code on startup of the host. The interface only adds the StartAsync and StopAsync methods (no ExecuteAsync method).
If we look at the source code for Microsoft.Extensions.Hosting we can see that when we call AddHostedService all it's doing is adding the IHostedService to the IServiceProvider.
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());
return services;
}
Then, inside Host.StartAsync it does some startup stuff, but the main thing is how it starts the IHostedService's.
public async Task StartAsync(CancellationToken cancellationToken = default)
{
// Omitted for brevity
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (IHostedService hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
if (hostedService is BackgroundService backgroundService)
{
_ = HandleBackgroundException(backgroundService);
}
}
// Omitted for brevity
}
Why show all this? I think it's important to know there is no real magic happening here. Whenever StartAsync on the host is called, all it does is call StartAsync on the IHostedServices. Conversely StopAsync on the IHostedService is only called when Host.StopAsync is called.
So what makes BackgroundService special?
BackgroundService
You might have noticed there is some special code when starting the IHostedService's checking if it's a BackgroundService. You have already posted a link to the source for BackgroundService, but I'll post the main parts here for visibility.
public virtual Task ExecuteTask => _executeTask;
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Create linked token to allow cancelling executing task from provided token
_stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executeTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executeTask.IsCompleted)
{
return _executeTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
As you can see, it's never actually awaiting on the Task returned from ExecuteAsync. This is what allows us to have ongoing tasks inside a BackgroundService. If you were to try doing a Task.Delay with a huge delay inside a normal IHostedService, your Host would never start properly.
In the situation that you return Task.CompleteTask from ExecuteAsync, it'll just return that complete task back to the StartAsync from Host.
So what's the point of doing all this with BackgroundTask's? Someone correct me if I'm wrong, but from looking at it, it seems like it only really exists for exception handling on long running Tasks. If we look at the HandleBackgroundException inside Host.StartAsync, we see this:
private async Task HandleBackgroundException(BackgroundService backgroundService)
{
try
{
await backgroundService.ExecuteTask.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.BackgroundServiceFaulted(ex);
}
}
All it does it takes the Task from ExecuteAsync and waits it, which allows us to see any exceptions thrown by sending it to the Logger.
Conclusions
Based on what I saw in the source code, I didn't see anything about your case that warrants a BackgroundService.
You've said 'It isn't an asynchronous API and as such doesn't need to awaited', which to me means you don't gain anything from BackgroundService.
You should just use a IHostedService and setup your callbacks in StartAsync, then remove them in StopAsync. There is no reason from what I can see this wouldn't work. It's not like your IHostedService is being stopped because it's not 'running' anymore.
If you wanted to stick with a BackgroundService, it's smarter to return Task.CompleteTask instead of awaiting a delay. The delay will still use some resources to keep the timer, even if that's small.

Schedule to run a method Daily 8 AM in ASP.NET MVC

how Call Method in ASP.NET MVC
For example, in one method, bring out a list of people who are born and send them a congratulatory message.
There is no code provided but generally, there are several options I could think of:
The built-in BackgroundService
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio
you can create a structure like this in the backgroundservice:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//Do work
await Task.Delay(timeSpan, stoppingToken);
}
}
Quartz task scheduler, which might be overkill for your task.
https://www.quartz-scheduler.net/
A long-running timer (not recommended)
Windows Task Scheduler Task on the Server, triggering an API Method.
(Suggested by Fildor)

What scenario is covered when not awaiting an async method and immediately evaluating IsCompleted?

The BackgroundService class contains the following code:
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;
}
I've read https://www.markopapic.com/csharp-under-the-hood-async-await/ which leads me to assume that all code of ExecuteAsync up to its first (if any) await ..., is executed before if (_executingTask.IsCompleted) is reached. So if any exception occurs in that part of ExecuteAsync, or if ExecuteAsync returns Task.CompletedTask, that will lead to executing return _executingTask;.
Is my understanding of this correct?
So if any exception occurs in that part of ExecuteAsync, or if ExecuteAsync returns Task.CompletedTask, that will lead to executing return _executingTask;
More generally, if ExecuteAsync completes synchronously, then StartAsync returns the task returned from ExecuteAsync.
In this particular case (with background services), I believe it's intended to handle things like precondition checks, which are generally done synchronously at the beginning of an asynchronous method. So if a background service synchronously determines it can't run, then StartAsync will return a faulted task.
This kind of code is extremely rare, and the design is questionable. E.g., if a background service asynchronously determines that it can't run, then there's no notification of that. I think the behavior would be more consistent to remove the whole if (_executingTask.IsCompleted) block, or else change the ExecuteAsync abstraction into separate InitializeAsync and ExecuteAsync parts.

Asynchronous webserver and garbage collection

After reading Stephen Cleary's article on async and asp.net, it is quite clear that using Async in web application is a big win on scalability (primarily due to non-blocking threads that are free to serve up more requests).
Although I am a little confused as to how does the Task object returned by the async operation remain in scope in an Aysnc WebServer implementation (lets say iis or self-hosted WebApi)
and not get collected.
for example,
If we have the following method in a low level Webserver implementation,
// some method that handles HttpListeners BeginContextAsync callback,
public void SomeHttpListenerCallback(HttpListenerContext context)
{
// Immediately set up the next begin context
// then handle the request
// forgive me if this is incorrect but this is my understanding of how an async request would be handled
// please feel free to point out the flaws
var task = messageHanlder1.SendAsync(context.Request, null);
task.ContinueWith(t => SendResponse(t));
}
public class MessageHandler1 : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = await base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
return response;
}
}
SendResponse being some method that sends the response to clients socket.
As you might have noticed, task object drops out of scope in SomeHttpListenerCallback, since no other thread (theoretically) has any reference to the task object, wouldnt it be marked for collection in next GC cycle?
I have read Jeffrey Ritcher's explanation (Clr via C#) of how compiler converts an aysnc method into a method with a state machine, however I fail to understand what happens to the task object if no thread waits on it.
And if a thread waits on it, wouldnt we have 1 blocked thread per request?
Any help to point me in the right direction would be greatly appreciated.
First of all, if you have a Task and nothing references it then it will be collected by the GC.
However, that's almost never the case. There are 2 types of tasks, Promise tasks and Delegate tasks.
Promise tasks (asynchronous) are mostly created by TaskCompletionSource or the compiler for an async method. In both cases someone holds a reference to the task so it will be able to complete it.
Delegate tasks (synchronous) however are referenced by the thread executing the delegate in them.
In your case task is referenced by the state machine behind SendAsync until it completes while task is referencing the continuation Task. When it completes the continuation is scheduled and so is referenced by the TaskScheduler and the thread executing SendResponse. When the operation completes the task will no longer be referenced and could be eventually GCed.
You can see an example in the implementation of Task.Delay where the Task (actually DelayPromise which inherits from Task) is being referenced by the System.Threading.Timer used to complete the Task.
Because some arguments don't actually need a timer you can see a distinction in memory usage between this:
static void Main()
{
while (true)
{
Task.Delay(int.MaxValue);
}
}
And this:
static void Main()
{
while (true)
{
Task.Delay(-1); // No Timer as the delay is infinite.
}
}
For every async operation that you do there is some "native" operation behind it - a socket read or a file IO and such. These are native CLR operations or PInvoke calls. When starting such an operation the framework registers a callback to be called on completion. This callback is being used to complete tasks. That callback is being kept alive by "the system". For example when you say myFileStream.BeginRead(myCallback) then the callback will be called even if it is otherwise unreferenced.
Speaking in a simplified way the native operation itself keeps everything alive that must be worked on when completion happens.
This is not a hard rule but an IO framework would be pretty useless if it called your callback only sometimes.
There is nothing that prevents any Task from being collected. For example var dummy = new TaskCompletionSource<object>().Task; is eligible for collection immediately.

fire and forget an async Task method sometimes not invoke

I have an async method:
public async Task DoSomethingAsync(){
...
await ...
await ...
....
return await SaveAsync();
}
In most time, I call this method by:
await DoSomethingAsync()
This call works as expected. But somewhere, I need to call this method as fire and forget:
public void OtherMethod()
{
...
DoSomethingAsync(); //fire and forget here
}
In this case, sometimes the Task DoSomethingAsync() runs and completes, but sometimes the task never invoked (or invoke some awaits within DoSomethingAsync() but never complete the last await SaveAsync();).
I'm trying to make sure the task will be invoked in fire and forget manner by these code:
public void OtherMethod()
{
...
Task.Factory.StartNew(() =>
{
await DoSomethingAsync();
}); //fire and forget again here
}
However, this does not work as expectation. So my questions are:
How to make the call to DoSomethingAsync() without await will always run and complete? (I don't care the case AppDomain restart/crash)
If I remove all async/await code within DoSomethingAsync() and replace await by .ContinueWith(), then the call to Task DoSomethingAsync() (not have async in method declaration) will be invoked and sure to complete (ignore case AppDomain restart/crash), if yes, how long from the call (I don't think that I'll be happy if the Task will be invoked after 10 minutes)?
You're probably getting an exception somewhere in DoSomethingAsync, which you cannot observe because you're ignoring the task. This is exactly the behavior you're asking for, since you're telling the code to "forget" the task.
To observe the exception, you cannot "forget" the task:
public Task OtherMethodAsync()
{
...
return DoSomethingAsync();
}
And at some point, await (or Wait) the returned task. That is how you know the task will run and complete.
The awaits should be working fine, so there is likely something else going on here that is holding up one or more of your methods being awaited. There are a few possibilities:
You have a deadlock somewhere in one of your methods, preventing it from completing and blocking the await from resuming.
All of your thread pool threads are blocking for some reason, preventing pending Tasks from running.
You're running the async method in a synchronization context and something is holding up the context, preventing it from running the dispatched callbacks. Typically the context would be the UI thread and generally this is pretty obvious, since it locks up your application UI.
If you can attach with the VS debugger and observe what's happening, try pausing and looking at the Parallel Stacks view. This should help narrow down which possibilities to consider.
As Stephen pointed out, it's also possible that an Exception is occurring when you're calling it fire-and-forget. If you're not already, make sure you handle TaskScheduler.UnobservedTaskException to log any events like this. Note that this is called from the finalizer, so the time at which it gets called is non-deterministic. This can make debugging tricky, since the event might not fire until much later than the actual event that caused the exception. As such, I recommend following Stephen's advice and saving the Task to await or Wait somewhere else later.
ASP.NET generally does not allow fire-and-forget (async void) operations to be kicked off from within a request. See https://stackoverflow.com/a/22484832/59641 for a further explanation of this behavior and some potential workarounds.

Categories

Resources