How to keep a hosted service alive in asp net core 3.1?
Asked Answered
D

5

9

how are you?.

I have a web api in net core 3.1, this in turn contains a service that every X minutes has to run to perform data migrations (I'm just testing it), but I have 2 problems.

  1. For the service to run, I must first run some url of my apis. The question is: How can I make this service start automatically, without the need to run any api?
  2. When I stop using the apis for a few minutes, the service stops working. The question is: How can I keep the service "Forever" alive?

I must emphasize that my web api is hosted in a web hosting, where I do not have access to all the features of IIS

This is my code, and in advance I appreciate your help.

MySuperService.cs

 public class MySuperService : IHostedService, IDisposable
{
    private bool _stopping;
    private Task _backgroundTask;
    private static readonly log4net.ILog log =log4net.LogManager.GetLogger(typeof(MySuperService));
    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("MySuperService is starting.");
        log.Info("MySuperService is starting.");
        _backgroundTask = BackgroundTask();
        return Task.CompletedTask;
    }

    private async Task BackgroundTask()
    {
        int contador = 1;
        while (!_stopping)
        {
            Console.WriteLine("MySuperService is working--> " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            log.Info("MySuperService is working--> " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            await Task.Delay(TimeSpan.FromMinutes(3));
            contador++;
        }

        Console.WriteLine("MySuperService background task is stopping.");
        log.Info("MySuperService background task is stopping.");
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("MySuperService is stopping.");
        log.Info("MySuperService is stopping.");
        _stopping = true;
        if (_backgroundTask != null)
        {
            // TODO: cancellation
            await BackgroundTask();
            //await _backgroundTask;
        }
    }

    public void Dispose()
    {
        Console.WriteLine("MySuperService is disposing.");
        log.Info("MySuperService is disposing.");
    }
}

Program.cs

public class Program
{
    private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Program));
    public static void Main(string[] args)
    {
        ...
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            }).ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<MySuperService>();
            });
}
Dint answered 6/2, 2020 at 0:27 Comment(1)
You should switch to worker service, devblogs.microsoft.com/aspnet/… and deploy it separately as Windows service.Langevin
L
0

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.0&tabs=visual-studio

If you inherit your infinite job service from the BackgroundService class and implement your logic inside a loop and the needed

await Task.Delay(TimeSpan.FromMinutes(x miutes))

the job will run as soon as the application starts without any API call, and stops when the app stops.

 public class MyService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("test");

            //await run job

            await Task.Delay(TimeSpan.FromSeconds(1));
        }
    }

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("start");

        await ExecuteAsync(cancellationToken);
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("stop");

        return Task.CompletedTask;
    }
}
Levana answered 6/2, 2020 at 13:30 Comment(2)
Why would you inherit from BackgroundService, then override the two methods that actually make it work?Deanery
this is wrong approach. StartAsync must return, due to IGenericHost design (hosted services are started before server Kestrel is). StartAsync must return asap (return Task.CompletedTask). You can verify this by injecting IHostApplicationLifetime and you will see that OnStarted is fired after you kill the app.Toney
D
5

Inherit from BackgroundService instead of implemented IHostedService. That will take care of the machinery of starting, running and stopping your service for you.

However the problem you are facing is that IIS isn't starting your c# process until the first request of the service. Then the default application pool settings will shut it down again if there are no requests. I'd suggest setting up some kind of scheduled task to periodically request a url and monitor that the service is running. You'll want to be notified if it stops anyway right?

Deanery answered 6/2, 2020 at 0:38 Comment(0)
W
4

Both IHostedService and BackgroundService can provide you long lasting background tasks. The main difference is that BackgroundService is easier to implements. However, your problem is caused by IIS default settings and is not related to your programming. The Application Pool is defaulted to terminate after 20 mins idle time. Change the following settings on your site application pool, and your problem will be solved.

  • Start Mode -> AlwaysRunning
  • Idle Time-out(minutes) -> 0
  • Idle Time-out Action -> Suspend

Actually, I recommend removing idle timeout in most situations. Although, it's the default but it's meant more for those with lots of hosting websites in order to keep the overall system performance.

However, if you have just a few production app pools on a server but occasionally don't have a visitor in a 20 minute space (i.e. overnight), you don't want your app pool to stop. Of course if you have enough resources to have all of your app pools running at once.

Application pool settings

Wendellwendi answered 14/12, 2023 at 18:37 Comment(0)
T
1

The approach I came up with goes as follows:

public class PingPongHostedService : IHostedService
{

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine(">>>>> Hosted service starting at {0}", DateTimeOffset.Now.ToUnixTimeMilliseconds());

        int count = 0;

        try
        {
            Task.Run(async () => {               // run in background and return completed task

                while (!cancellationToken.IsCancellationRequested)
                {
                    await Task.Delay(1_766, cancellationToken);
                    Console.WriteLine("loop no. {0}", ++count);
                }

            }, cancellationToken);
            
        }
        catch (OperationCanceledException e){} // Prevent throwing if the Delay is cancelled
        
        return Task.CompletedTask; // return ASAP
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine(">>>>> Hosted service stopped at {0}", DateTimeOffset.Now.ToUnixTimeMilliseconds());
        return Task.CompletedTask;
    }
}

The important thing here is: StartAsync must exit ASAP, so that IGenericHost bootstrap can continue. That's why I'm using Task.run to transfer real work to another thread, allowing caller thread to continue.

Toney answered 29/9, 2020 at 6:55 Comment(0)
L
0

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.0&tabs=visual-studio

If you inherit your infinite job service from the BackgroundService class and implement your logic inside a loop and the needed

await Task.Delay(TimeSpan.FromMinutes(x miutes))

the job will run as soon as the application starts without any API call, and stops when the app stops.

 public class MyService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("test");

            //await run job

            await Task.Delay(TimeSpan.FromSeconds(1));
        }
    }

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("start");

        await ExecuteAsync(cancellationToken);
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("stop");

        return Task.CompletedTask;
    }
}
Levana answered 6/2, 2020 at 13:30 Comment(2)
Why would you inherit from BackgroundService, then override the two methods that actually make it work?Deanery
this is wrong approach. StartAsync must return, due to IGenericHost design (hosted services are started before server Kestrel is). StartAsync must return asap (return Task.CompletedTask). You can verify this by injecting IHostApplicationLifetime and you will see that OnStarted is fired after you kill the app.Toney
S
-4

I'm well aware that it is quite an old question and there have been already a few answers already but I would like to provide my input on the matter. I have used a brilliant library to schedule tasks called FluentScheduler. Before we start, add this in your Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
     services.AddHostedService<AppLifetimeEventsService>();
}

This is how I solved the above issue:

    public class AppLifetimeEventsService : IHostedService
    {
        private readonly ILogger _logger;
        public AppLifetimeEventsService(IServiceProvider services)
        {
            _logger = services.GetRequiredService<ILogger<AppLifetimeEventsService>>();
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("The Web API has been started...");

            //FluentScheduler
            var registry = new Registry();
            //For example let's run our method every 1 hour or 10 seconds
            registry.Schedule(async () => await SomeBackgroundTask()).ToRunNow().AndEvery(1).Hours();
            //registry.Schedule(async () => await SomeBackgroundTask()).ToRunNow().AndEvery(10).Seconds();

            //FluentScheduler
            JobManager.Initialize(registry);

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            //Needed to remove all jobs from our Job manager when our Web API is shutting down  
            JobManager.RemoveAllJobs();

            _logger.LogInformation("The Web API is stopping now...");

            return Task.CompletedTask;
        }

        private async Task SomeBackgroundTask()
        {
             //Your long task goes here... In my case, I used a method with await here.
        }
    }

If you want to run a method every X seconds/minutes/hours/days/etc. you will have to implement IHostedService: info and docs. However, if you want to execute the method only once, you should implement BackgroundService.

Shadwell answered 4/3, 2021 at 20:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.