Code Monkey home page Code Monkey logo

betterhostedservices's Introduction

Better Hosted Services

GitHub Actions Status GitHub Actions Build History

BetterHostedServices is a tiny library (<500 lines of code not including tests) that aims to improve the experience of running background tasks in ASP.NET Core. You can read more details about the issues and warts IHostedService and BackgroundService has here.

** Note that this library is currently not actively maintained. Newer versions of the .NET Core properly crash the application when an uncaught error bubbles up from a BackgroundService. You might still find some use in the implementation of periodic tasks, but for the most part this library isn't needed anymore. **

Installation

From nuget:

dotnet add package BetterHostedServices

And then call services.AddBetterHostedServices() inside your Startup.cs's ConfigureServices

BackgroundService, Error handling and CriticalBackgroundService

Microsoft recommends extending from BackgroundService for long running tasks. However BackgroundServices fails silently if an uncaught error is thrown.

That means this example will not throw an error but simply fail silently.

public class YieldingAndThenCrashingCriticalBackgroundService: BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Yield(); // Or some other async work
        throw new Exception("Oh no something really bad happened");
    }
}

We can do better.

Using BetterHostedServices you can inherit from CriticalBackgroundService instead of the regular BackgroundService.

If an uncaught error happens in a CriticalBackgroundService it will be logged, and it will crash the application.

You can use it like this:

public class YieldingAndThenCrashingBackgroundService: CriticalBackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Yield(); // Or some other async work
        throw new Exception("Oh no something really bad happened");
    }
    
    public YieldingAndThenCrashingBackgroundService(IApplicationEnder applicationEnder) : base(applicationEnder) { }
}

And then you can use it like any other IHostedService. E.g. inside ConfigureServices you add the following:

services.AddBetterHostedServices();
services.AddHostedService<YieldingAndThenCrashingCriticalBackgroundService>();

That's it! Your CriticalBackgroundService now stops the application if an error happens

Customizing error handling in CriticalBackgroundService

If you need to customize error logging or handle the error in another way, you can override the OnError method.

public class YieldingAndThenCrashingCriticalBackgroundService : CriticalBackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Yield(); // Hand over control explicitly, to ensure this behaviour also works
        throw new Exception("Crash after yielding");
    }

    protected override void OnError(Exception exceptionFromExecuteAsync)
    {
        // Add your custom logging here
        this._applicationEnder.ShutDownApplication(); // or simply call base.OnError
    }

    public YieldingAndThenCrashingCriticalBackgroundService(IApplicationEnder applicationEnder) : base(applicationEnder)
    {
    }
}

AddHostedServiceAsSingleton

Hosted Services and BackgroundServices aren't part of the dependency injection container. This means that you can't get them injected into your services or controllers. If you need to do this, you can use the AddHostedServiceAsSingleton extension method on the IServiceCollection

services.AddHostedServiceAsSingleton<ISomeBackgroundService, SomeBackgroundService>();

After that, you can inject them via the DI container just like any ordinary singleton.

RunPeriodicTasks

If you simply want your BackgroundService to run a periodic tasks, there's some boilerplate you generally have to deal with. Some best-practices for using BackgroundServices to run periodic tasks are documented here - but we provide a shortcut here.

If you want to run a periodic task, implement the IPeriodicTask interface, and use the IServiceCollection.AddPeriodicTask method, like below.

public class MyPeriodicTask: IPeriodicTask
    {
        public async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // Do some businesss logic
        }
    }
# In ConfigureServices
services.AddPeriodicTask<MyPeriodicTask>(failureMode: PeriodicTaskFailureMode.CrashApplication, timeBetweenTasks: TimeSpan.FromSeconds(5));

You can determine two different Failure Modes:

  • PeriodicTaskFailureMode.CrashApplication : If this is set and the task throws an uncaught exception, the exception will bubble up and crash the application. This is similar to how an uncaught exception inside a CriticalBackgroundService works. Use this if you do not expect your tasks to fail, and it would be unwise to continue if one did.
  • PeriodicTaskFailureMode.RetryLater : If you expect that tasks might fail occasionally, and that retrying is safe, use this method. It will log the error and run a new task after timeBetweenTasks has elapsed.

The parameter timeBetweenTasks is how long until after the completion of the previous task, the next one should start. The clock will not start ticking until the previous task has completed so there is no risk of tasks overlapping in time.

betterhostedservices's People

Contributors

geewee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

betterhostedservices's Issues

Re-enable tests for Windows and MacOS

For some reason tests that work fine on Linux fails on Windows - and I can't figure out whether or not it's due to the tests being broken, or platform differenes.

End process *less* gracefully

Describe the feature

It'd be nice if there was an option to end the process less gracefully as opposed to IHostApplicationLifetime.StopApplication()

But.... why?

In my scenario, I'm trying to apply this to a Windows service, which has recovery options in the event of a service failure. In my case specifically I want Windows to restart the service (after a short delay) when it encounters an exception. When implementing this packages as-is, it fails successfully in a way that the recovery options don't trigger.

To be honest I'm not sure exactly the best way to proceed. I'm not sure if using a non-zero exit code would help. I can run some tests tomorrow to see what options are available.

This site seems to suggest that a non-zero exit code won't trigger this, but it was written in 2008 so maybe things have changed. https://mdenomy.wordpress.com/2008/02/28/using-the-automatic-recovery-features-of-windows-services/

PS nice work! I really like the simplicity and I'm glad someone put a workaround in place until 6.0 (hopefully) adds the stop-on-exception feature that this addresses.

Packages on release instead push

Question

Maybe better create NuGet and GitHub packages on release, instead of pushing to main? It will prevent packages with potentially useless changes

Question about default logging on service crash

Question : The current default implementation of CriticalBackgroundService.OnError log to the console. Is this a design choice ?

I'm using your library in one of my windows service application (from a worker service) and a few days ago it crashed. In my log file, I only had "INFO ... Microsoft.Hosting.Lifetime - Application is shutting down...". No reason why. From the lines before the shutdown, I could guess that it crashed during an operation inside the CriticalBackgroundService and no log/exception was written.
I found out that the CriticalBackgroundService.OnError will not log to an ILogger, but to the console. Obviously the console is not redirected to my log file. I'm using Microsoft ILogger, and my ILogger is configured to output to log4net, which log to a file and also the console.
I could think that you're using console because you're targetting a basic program, without any ilogger and all the DI stuff. However that can't be the case because you're already registering your services with the microsoft DI in the readme file and the HostedService you're trying to fix are from asp.net / worker.


I've implemented the virtual OnError in all my background service to log to my ILogger, so i'm fine with the rest and I don't require any modification from your part. I was kinda not expecting it to log to console, but instead to log to ILogger by default.

Is it by design that CriticalBackgroundService log to console, while PeriodicTaskRunnerBackgroundService use the ILogger facility ?

When I check the rest of your project, PeriodicTaskRunnerBackgroundService seems to use ILogger to report crash, but CriticalBackgroundService only use the console. Perhaps it's because it's critical ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.