DEV Community

Cover image for Implement a Time-based Service in .NET 8 using IHostedService Interface🔍
Kaustubh Joshi
Kaustubh Joshi

Posted on

Implement a Time-based Service in .NET 8 using IHostedService Interface🔍

👓 Introduction

Just like we have setInterval() in JavaScript, .NET 8 provides a very helpful interface known as IHostedService in order to create a custom, independent, background running, timer interval based Services, where you can write your logic which will run after every configured interval of time.

This post is a quick tutorial on What is IHostedService in .NET, How to use it to create timer based event services, What to avoid while implementing the same and How this interface is different from the BackgroundService Class.


🖼 What exactly is IHostedService Interface?

IHostedService is a .NET Interface, that is a part of .NET Hosting Infrastructure.
You can develop cross-platform background services which are logging, configuration, and dependency injection (DI) ready.
Implementing IHostedService interface allows you to create long-running services that can be managed by the .NET host.

The interface defines two methods, managed by .NET Host:

  • Task StartAsync(CancellationToken cancellationToken):

    • Triggered when the application host is ready to start the service.
    • Input parameter - cancellationToken, which Indicates that the start process has been aborted.
    • Returns a Task that represents the asynchronous Start operation.
  • Task StopAsync(CancellationToken cancellationToken):

    • Triggered when the application host is performing a graceful shutdown.
    • Takes Input parameter - cancellationToken, which Indicates that the shutdown process should no longer be graceful.
    • Returns a Task that represents the asynchronous Stop operation.

🎭 IHostedService vs. BackgroundService

  • IHostedService:

    • Interface provided by .NET
    • Write your complex/custom logic
    • Fine-grained control over the service lifecycle
    • Prefer using when you need to manage the start and stop logic explicitly
  • BackgroundService:

    • Abstract Class implements IHostedServices
    • Overrides the ready-made ExecuteAsync() method
    • Simplification using higher level Abstraction over IHostedService
    • Prefer using when implementing long-running tasks

IHostedService vs BackgroundService


⌨ Example: Implementing a Timer-Based Service in .NET 8

1. Create a New Project

dotnet new worker --name TimedHostedServiceExample
Enter fullscreen mode Exit fullscreen mode

2. Create a New Class Named TimedHostedService.cs


using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public class TimedHostedService: IHostedService, IDisposable
{
    // Logger instance
    private readonly ILogger<TimedHostedService> _logger;
    // DI ready Timer
    private Timer _timer;

    public TimedHostService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timer Service is starting.");
        // Timer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period);
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return Task.CompletedTask;
    }

    // DoWork is where you will write your business logic
    private void DoWork(object state)
    {
        _logger.LogInformation("Timer Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timer Service is stopping.");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        // Do not forget disposing the Timer instance to avoid any memory leaks
        _timer?.Dispose();
    }
}

Enter fullscreen mode Exit fullscreen mode

3. Register the Service:

In the Program.cs file, register the TimedHostedService with the host.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder(args).ConfigureServices(services => {
   services.AddHostedService<TimedHostedService>();
}).Build();

await host.RunAsync();

Enter fullscreen mode Exit fullscreen mode
If you are using AutoFac as your .NET IOC container, then 👇🏻
containerBuilder.RegisterType<TimedHostedService>().As<IHostedService>().InstancePerDependency();
Enter fullscreen mode Exit fullscreen mode

4.Output

info: App.TimedHostedServiceExample.TimedHostedService[0]
      Timer Service is starting.
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: .\TimedHostedServiceExample
info: App.TimedHostedServiceExample.TimedHostedService[0]
      Timer Service is working.
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: App.TimedHostedServiceExample.TimedHostedService[0]
      Timer Service is stopping.
Enter fullscreen mode Exit fullscreen mode

🧐 Avoid doing these mistakes

  1. Blocking the StartAsync Method: Make sure the logic you put in your StartAsync code completes quickly. If this is not the case then the DoWork logic should be handled Asynchronously.
  2. Not Handling Cancellation Properly:Always respect the CancellationToken to ensure graceful shutdowns.
  3. Not disposing the Timer: Make sure to dispose the Timer instance to avoid memory leaks.

🌱 Handle the DoWork logic Asynchronously to prevent a blocking long-running call

So, in your use case, it might happen that the logic in DoWork method call, needs to be Asynchronous, i.e. it should not block the main thread until the business logic has completed its processing.

To ensure that the DoWork method does not block the main thread, you can use the Task.Run method to offload the work to a background thread. This will make main thread free and responsive for any further actions.
Below is the DoWork with Task.Run method:

private void DoWork(object state)
{
    _logger.LogInformation("DoWork started...");
    Task.Run(async () =>
    {
        await Task.Delay(1000); // Simulating some async work
        _logger.LogInformation("Asynchronous work completed.");
    });
}
Enter fullscreen mode Exit fullscreen mode

When to use the non-blocking logic?

  • I/O bound operations
  • HTTP calls
  • Any task where Async execution is needed

🔮 Conclusion

Implementing background time-interval based services using IHostedService in .NET 8 provides a powerful way to manage your timer logic.
By understanding the basics, avoiding common pitfalls, and choosing the right abstraction for your needs, you can effectively integrate background processing into your applications.

Feel free to experiment with the provided example and explore the official documentation for more advanced scenarios.
Happy coding! ❣


🧩References

Top comments (12)

Collapse
 
govind_prajapati_3707ef68 profile image
Govind Prajapati

Nice.

Collapse
 
elpidaguy profile image
Kaustubh Joshi

Thanks!!

Collapse
 
peter_truchly_4fce0874fd5 profile image
Peter Truchly

Nice tutorial. Did you consider using Quartz.NET?

Collapse
 
elpidaguy profile image
Kaustubh Joshi

Thank you!!!
And no, I have not given Quartz a shot yet.

Do you recommend using it? :)

Collapse
 
peter_truchly_4fce0874fd5 profile image
Peter Truchly

It is quite popular for a long time, I know it from enterprise Java world. Personally, I prefer minimalism whenever it is enough (like with Timer), Quartz is really an enterprise way how to schedule something :D, just a taste of configuration expressions:

0 0 6,19 ? * * = 6:00 AM and 7:00 PM every day.
0 0/30 8-10 ? * * = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.
0 0 9-17 * * MON-FRI = on the hour nine-to-five weekdays.

It could be a good topic for another article though.

Thread Thread
 
elpidaguy profile image
Kaustubh Joshi • Edited

Awesome!!! I need to give it a try now, very clean syntax, like cron jobs. Thanks again.

Collapse
 
mrunal_kulkarni_579fcc30c profile image
MRUNAL KULKARNI

Nice

Collapse
 
elpidaguy profile image
Kaustubh Joshi

Thank you :)

Collapse
 
abhay_malviya_72c651605ac profile image
Abhay Malviya

Good topic

Collapse
 
elpidaguy profile image
Kaustubh Joshi

Thank you Abhay!!

Collapse
 
mihir_patel_9938b0385ea07 profile image
Mihir Patel

Can we run this on a recurring basis, specifically every Monday at midnight?

Collapse
 
elpidaguy profile image
Kaustubh Joshi

Hello Mihir,

This Interface should solve your issue of running some logic on recurring manner.
But as far as this code goes, you cannot specify the day and time directly.

For example,
I cannot tell the code that, please run the DoWork logic specifically on Monday at 12AM IST.

Instead,
I can add the Timer Due Period as,
new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromDays(7));

This will run every 7 days immediately after you deploy it.
Although, here you will need to deploy it on Monday 12AM, so that it will run recurrently every 7 days.

You can also add offset time as,
new Timer(DoWork, null, TimeSpan.FromHours(5), TimeSpan.FromDays(7));
So, you can deploy it 5 hours before 12 AM and then it will keep running it after every 7 Days.

You can always create a custom TimeSpan event by subtracting the current time with your desired time, i.e. Monday 12AM logically.

Please feel free to experiment more with examples to get your desired output.
Hope this helps!!!