Hosted Services in the .NET Core world mean background tasks in everyday developer terms. If you’re living in the C# world, and even the Azure world, you actually already have a couple of options for doing background style tasks. Problems that you can solve using Hosted Services are probably similar to the problems you currently solve using Windows Services or Azure WebJobs. So first, let’s take a look at some comparisons.
Hosted Services vs WebJobs
WebJobs are similar to hosted services in that they run on the same machine as an Azure Website – thus sharing resources. WebJobs do have the added benefit of being able to be deployed separately (Hosted services are part of the website) and have additional functionality when it comes to running as a singleton. With Hosted Services, there is an instance running of that hosted service for every deployment of your website which can be an issue if you only want one instance of that “process” running at anytime. You can program around this by creating your own locking mechanism, but obviously webjobs gets this out of the box. The one other difference really is that with the WebJobs SDK, you get things like queue/blob triggers right away. Inside a ASP.NET Core Hosted Service, you would need to write all of this manually.
Hosted Services vs Windows Services
Windows Services are typically hosted on other infrastructure that isn’t also hosting your website. They can be deployed independently, and don’t have really any tie in to your website. But that also comes as a negative. If you are using PAAS on something like Azure or Google Cloud, you would then need a separate VM to hosted your Windows Service. Not too great!
Hello World Hosted Service
Create a new class in your .NET Core Web Project called “HelloWorldHostedService”. This is going to be your first piece of code for your Hosted Service :
public class HelloWorldHostedService : IHostedService { private Timer _timer; public Task StartAsync(CancellationToken cancellationToken) { _timer = new Timer(HelloWorld, null, 0, 10000); return Task.CompletedTask; } void HelloWorld(object state) { Debug.WriteLine("Hello World!"); } public Task StopAsync(CancellationToken cancellationToken) { //New Timer does not have a stop. _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } }
So all we are doing is starting a timer that every 10 seconds, prints out “Hello World!” to the debug output.
You’ll notice that we have a start and stop, probably pretty similar to a windows service where the StartAsync method is when our web process kicks off. It shouldn’t be where we do work, but instead where we setup side threads to do the work. And stop is where our web process stops, so we obviously have to pause our timer.
You’ll also need the following line, in your ConfigureServices method, in your startup.cs.
public void ConfigureServices(IServiceCollection services) { services.AddHostedService<HelloWorldHostedService>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
And that’s it, that’s your first hosted service. If you run your web project, your service should kick off (Either set a breakpoint or check the Debug output), and away you go!
Background Service Helper
The thing is, each time you write a background service, it’s likely to be the same sort of process. It’s probably going to either be something on a timer, or in an infinite loop. So 9 times out of 10, you’re going to be using some sort of boiler plate that kicks off your task on a different thread, and handles cancellation tokens etc.
Well Microsoft foresaw this, and create an abstract helper class called BackgroundService. We can rewrite our above code to instead look like :
public class HelloWorldHostedService : BackgroundService { protected async override Task ExecuteAsync(CancellationToken stoppingToken) { while(!stoppingToken.IsCancellationRequested) { Debug.WriteLine("Hello World"); await Task.Delay(10000, stoppingToken); } } }
Notice that we now inherit from BackgroundService and not from IHostedService. But our startup.cs call doesn’t change.
Behind the scenes, the code for BackgroundService looks like (And you can see the full source on Github here) :
public abstract class BackgroundService : IHostedService, IDisposable { private Task _executingTask; private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); protected abstract Task ExecuteAsync(CancellationToken stoppingToken); public virtual Task StartAsync(CancellationToken cancellationToken) { _executingTask = ExecuteAsync(_stoppingCts.Token); if (_executingTask.IsCompleted) { return _executingTask; } return Task.CompletedTask; } public virtual async Task StopAsync(CancellationToken cancellationToken) { if (_executingTask == null) { return; } try { _stoppingCts.Cancel(); } finally { await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); } } public virtual void Dispose() { _stoppingCts.Cancel(); } }
So it’s actually pretty simple. It’s just kicking off a thread with your work, and waiting for it to finish (If it finishes). It’s handling the cancellation tokens for you, and a bit of cleanup.
Good Examples Hosted Services
I thought I would end on what makes a good hosted service. It should be something preferably specific to that instance of the website. If you scale out horizontally, each instance of the hosted service shouldn’t compete with each other and cause issues. So things like clearing a shared cache, moving files/data, ETL processes etc may not be great candidates for hosted services because each service will be competing with each other.
An example of a service I wrote recently was that I built an API that read data from a secondary replica database. I didn’t mind if the replica fell behind in updates from the primary, but it still couldn’t fall too far (e.g. More than 15 minutes). If it fell more than 15 minutes, I wanted to force my API to instead read from the primary. Each website checking the secondary status and swapping to read from primary is very scalable because the check itself is atomic and no matter how many times it’s called, it doesn’t interfere with each other.
Officially it’s ASP.NET Core 😉
Just found out your site. Great posts. One quick question – Can i use background service for something like sending email reminders/SMS reminders. Is it good practice ? I am working on client project and don’t wan’t to do bad decisions. Thanks…
It’s probably a quick and dirty way to get up and running (e.g. have things run on timers). It’s not going to be scalable however because the amount of emails/sms you need to send might take longer than the minute timer you have going. But in terms of getting up and running it’s probably a good use case.
Yes and No; bluntly said it’s a in process application so when you are hosting it in a non dev environment you will have multiple processes spawn. So you cannot use a external datastore for that.
You probably can however make a queue in the current process; and by using that you can send those mail without interfering with your thread… HOWEVER! you cannot expect that the process will not close. Once it does you will lose those emails in the queue. I do recommand you to find another solution for that problem.
Can you suggest some solutions to tackle data loss problem from queue? How can we add fault tolerance, suppose when the system itself goes down, we lose the data in memory.
Thanks for reply. For scalability what will be best practice ? Is Sqltabledependency any good ?
It’s mostly going to be about concurrent throughput when it comes to sending email/sms. For that you’re likely going to need a queue + something like an Azure Function/Worker/WebJob that can scale out without also scaling out your web app.
SQLTableDependency is almost like change tracking/tailing a set of records. Which is OK. But again, you might end up with more messages than one single worker can send and you would need to scale out.
So I went through this article and I’ve implemented something similar, then I looked at your article about The Factory Pattern (https://dotnetcoretutorials.com/2019/10/15/the-factory-pattern-in-net-core/). Anyway, my question is this, I’ve implemented the IHostedService method as defined above but I created a constructor that accepts an ILogger because I would like my hosted service to have some logging functions. So it looks like HelloWorldHostedService(ILogger logger){ _logger = logger; } with _logger defined as a private readonly ILogger variable in the HelloWorldHostedService class.
Obviously this does not fly, as I wouldn’t be writing here if everything worked out. How does one go about including logging functionality on HostedServices? What I have done is that I’ve followed the MS documents for creating logs (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1) so everything regarding the logging is configured in the CreateHostBuilder(..) function in the Program.cs.
It should work, in this example you’re adding to a web application so the CreateDefaultBuilder should still occur. If you mean that you are trying to create a Windows Service without the web part (Like just a background worker), there is a different guide here : https://dotnetcoretutorials.com/2019/12/07/creating-windows-services-in-net-core-part-3-the-net-core-worker-way/. In .NET Core 3.0 Microsoft introduced the “Worker” template which is basically just another way of doing Windows Services, but it again uses the CreateDefaultBuilder which according to the source code (https://github.com/dotnet/aspnetcore/blob/1480b998660d2f77d0605376eefab6a83474ce07/src/DefaultBuilder/src/WebHost.cs#L155) should set up logging for you.
Hello,
I wonder if it is possible to deploy such hosted service worker (for ex. constant running one) in IIS (which would handle restarts for ex.). As this is more like a console application, I suppose it is not possible? Any suggestions?
If you are looking for a pure “worker” then you can turn a Hosted Service into a Windows Service (https://dotnetcoretutorials.com/2019/12/07/creating-windows-services-in-net-core-part-3-the-net-core-worker-way/). That’s probably more along the lines of what you need.
Thanks, for the fast response. So in general is it possible to use hosted service through IIS, as worker without having it included on web application? In other words, if I understand correctly such hosted services should only be used for triggering simple task processing through web application endpoint (not as a standalone application)?
Well theoretically, you could host a website that has no end points in IIS and have it all worker background tqasks, but I’m not sure *why* you would unless you want to deploy everything like a website everytime instead of the added complication of windows services.
Hi Wade,
Thanks for the article. Silly question but is it possible to add more than one hosted service. I tried the same but for some reason the ones added below the first service wouldn’t fire.
You can but they get started one by one. So if your start method has a loop (and never returns), it will not start others. You need to kick off a background thread to do the work then return Task.CompletedTask etc. If you use the “BackgroundService” helper abstract class, this will do it mostly for you.