Running a job on a schedule is a common and essential requirement in programming. All the major technologies and programming languages give developers a way to run a scheduler service, and .NET is no different.
.NET has popular libraries such as TopSelf, Quartz, Hangfire etc. that you can use to run your scheduled jobs. If you are on the cloud, you can leverage serverless solutions such as Azure Function, AWS Lamda and Google Cloud Functions to schedule your jobs.
However, there are occasions where we may want to keep things simple and avoid any external dependency.
This post explains how you can create a scheduler service using .NET BackgroundService.
You can follow this GitHub repository for the source code of the sample application. I’m using the .NET 5 SDK for the sample, but the solution should also work with previous versions of the .NET Core.
Let us first start with creating a Worker project with the default template that contains a BackgroundService, as shown below:
Running the application would return an output similar to below:
Next, we will create a recurring task using the CRON expression. Here, I have used the popular open-source library CRONOS to parse the CRON expression. In the below code, we have scheduled our background service to run every one minute.
Here is the console output after the change:
Running Scheduler as a Scoped Service
The BackgroundService is a Singleton service. However, it may not be an ideal place to execute our recurring job with a scheduler since we may have some scoped dependencies, such as a database repository. Injecting a scoped or transient dependency in Singleton service could lead to Captive Dependency. To fix this, we can invoke a scoped service within a BackgroundService.
To create a scoped scheduler service, we first need to create a scoped service and our scheduler job logic.
Next, we update BackgroundService to inject IServiceProvider and resolve the scoped service created in the previous step.
Last but not least, we have to register our newly created scoped service in Program.cs.
That’s it! We now have the flexibility to invoke a recurring job within a scope.
A word of caution: The above code works well as long as you only have a single instance of your worker. However, if your worker has more than one instances deployed, it would run the job multiple times. To avoid this, you could use techniques like a distributed lock. I will talk more about this in a separate post.
This post explains how you can create a simple scheduler service without using an external library or serverless functions. I hope you find it useful. 🙂