'ASP.NET Core IHostedService manual start/stop/pause(?)

I would like to implement a recurring (timed) IHostedService instance in ASPNET Core that can be stopped and started on demand. My understanding is that IHostedService(s) are started by the framework on application startup.

However, I would like to be able to start/stop the service 'manually', perhaps using an on/off toggle via a UI. Ideally the "off" state would dispose of currently running service, and the "on" state would then create a new instance.

I've read the MS docs here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1.

My initial thought was to get an instance of the running service and then call the public StopAsync(CancellationToken token) method. However I'm a little stuck when it comes to which token I should pass in, and the same could be said for the StartAsync(CancellationToken cancellationToken) method.

Any ideas on how this should be done, or if it's even advisable? Is my approach somehow going against the intended design of hosted services in ASPNET Core?

EDIT 7.27.2018

So it appears after some more research (aka actually reading the documentation :D) that hosted services StartAsync/StopAsync methods are indeed meant to coincide with the lifetime of the application. Registered IHostedServices seem to not be added to the DI container for injection into other classes.

Therefore I do not think my initial idea will work. For now I registered my services with configuration dependencies (IOptions<T>) that can be updated at runtime. As the hosted services is processing, it will check the configuration to see if it should continue, otherwise it will just wait (instead of stopping or disposing of the hosted service).

I'll probably mark this as my answer soon, unless I hear of some other ideas.



Solution 1:[1]

For StopAsync(CancellationToken token), you could pass new System.Threading.CancellationToken(). In the defination of public CancellationToken(bool canceled), canceled indicates state for the token. For your scenario, there is no need to specify the canceled since you want to Stop the service.

You could follow below step by step:

  1. Create IHostedService

       public class RecureHostedService : IHostedService, IDisposable
     {
    private readonly ILogger _log;
    private Timer _timer;
    public RecureHostedService(ILogger<RecureHostedService> log)
    {
        _log = log;
    }
    
    public void Dispose()
    {
        _timer.Dispose();
    }
    
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _log.LogInformation("RecureHostedService is Starting");
        _timer = new Timer(DoWork,null,TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return Task.CompletedTask;
    }
    
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _log.LogInformation("RecureHostedService is Stopping");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }
    private void DoWork(object state)
    {
        _log.LogInformation("Timed Background Service is working.");
    }
    }
    
  2. Register IHostedService

        services.AddSingleton<IHostedService, RecureHostedService>();
    
  3. Start and Stop Service

     public class HomeController : Controller {
     private readonly RecureHostedService _recureHostedService;
     public HomeController(IHostedService hostedService)
     {
         _recureHostedService = hostedService as RecureHostedService;
     }
     public IActionResult About()
     {
         ViewData["Message"] = "Your application description page.";
         _recureHostedService.StopAsync(new System.Threading.CancellationToken());
         return View();
     }
    
     public IActionResult Contact()
     {
         ViewData["Message"] = "Your contact page.";
         _recureHostedService.StartAsync(new System.Threading.CancellationToken());
         return View();
     } }
    

Solution 2:[2]

Using Blazor Server, you can start and stop background services in the following ways. Asp.net Core MVC or Razor is the same principle

First, implement an IHostService

public class BackService : IHostedService, IDisposable
{
    private readonly ILogger _log;
    private Timer _timer;
    public bool isRunning { get; set; }
    public BackService(ILogger<V2rayFlowBackService> log)
    {
        _log = log;
    }

    public void Dispose()
    {
        _timer.Dispose();
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _log.LogInformation($"begin {DateTime.Now}");
        _timer = new Timer(DoWorkAsync, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        isRunning = false;
        _log.LogInformation($"{DateTime.Now} BackService is Stopping");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }
    private void DoWorkAsync(object state)
    {
        _log.LogInformation($"Timed Background Service is working.  {DateTime.Now}");
        try
        {
            isRunning = true;
            // dosometing you want
        }
        catch (Exception ex)
        {
            isRunning = false;
            _log.LogInformation("Error {0}", ex.Message);
            throw ex;

        }
    }
}

Registration Service In Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<BackService>();
        services.AddHostedService(sp => sp.GetRequiredService<BackService>());
    }

Inject background services into Blazor components

public class IndexBase:ComponentBase
{
    [Inject]
    BackService BackService { set; get; }

    protected override void OnInitialized()
    {
        if (BackService.isRunning)
        {
            BackService.StopAsync(new System.Threading.CancellationToken());
        }
        base.OnInitialized();
    }
    public void on()
    {
        if (!BackService.isRunning)
        {
            BackService.StartAsync(new System.Threading.CancellationToken());
        }

    }
    public void off()
    {
        if (BackService.isRunning)
        {
            BackService.StopAsync(new System.Threading.CancellationToken());
        }

    }
}
@page "/"
@inherits IndexBase
<h1>Hello, world!</h1>

Welcome to your new app.

<button @onclick="on">Start</button>
<button @onclick="off">Stop</button>

reference

Solution 3:[3]

You should inherit your HostedService from your own Interface and register your service as Singleton, but in a difference way:

First register your service with AddHostedService generic method.

        services.AddHostedService<TimerHostedService>();

Then add a public static field to your class named Instance, that holds the instance reference of your class and set its value in the constructor!

Then put a factory in the ConfigureServices for registering your service as singleton that returns the static instance field !

Here is the sample code: (in your HostedService.cs: )

public interface ITimerHostedService : IHostedService
{

}

public class TimerHostedService : ITimerHostedService
{
    private static TimerHostedService _instance;

    public static TimerHostedService Instance => _instance;

    public TimerHostedService(ILogger<TimerHostedService> logger)
    {
        if(_instance == null)
        {
            _instance = this;
        }
    }
}

And here is the code for registering your service as singleton ( in Startup.cs ):

        services.AddHostedService<TimerHostedService>();

        services.AddSingleton<ITimerHostedService, TimerHostedService>(serviceProvider =>
        {
            return TimerHostedService.Instance;
        });

And here is the code in your Controller to manually start / stop your HostedService:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly ITimerHostedService _hostedService;

    public HomeController(ILogger<HomeController> logger, ITimerHostedService hostedService)
    {
        _logger = logger;
        _hostedService = hostedService;
    }

    public async Task<IActionResult> Start()
    {
        await _hostedService.StartAsync(default);
        
        return Ok();
    }

    public async Task<IActionResult> Stop()
    {
        await _hostedService.StopAsync(default);
        
        return Ok();
    }
}

Happy Coding!

Enjoy Your Lovely Moments :X

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Edward
Solution 2 Alessio
Solution 3