How to start HostedService in MVC Core app without http request
Asked Answered
H

5

18

In my MVC .NET core 2.2 app there is HostedService which doing background work.

It is register in ConfigureServices method of Startap class

services.AddHostedService<Engines.KontolerTimer>();

Since this is background service independent of users requests I want to start my background service immediately when app starts. Now is case to my HostedService staring after first user request.

What is proper way to start HostedService when MVC Core app start

My serivce looks like this one https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

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

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

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

Looks like I have problem staring app at all.

My porgram cs looks like

public class Program
    {
        public static void Main(string[] args)
        {
           CreateWebHostBuilder(args).Build().Run();


        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .UseSerilog((ctx, config) => { config.ReadFrom.Configuration(ctx.Configuration); })
            .UseStartup<Startup>();
    }

And I do not hit any break point before first user request. Am I miss something, this is default .Net Core app created by VS2017

Here is my starup.cs

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }
        private Models.Configuration.SerialPortConfiguration serialPortConfiguration;

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
                .AddDefaultUI(UIFramework.Bootstrap4)
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.AddDbContext<Data.Parking.parkingContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));


         services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddHostedService<Engines.KontolerTimer>();}
Hypervitaminosis answered 16/1, 2019 at 8:31 Comment(3)
How did you host it? I am facing trouble in deploying HostedService in IISConfederacy
@AdritaSharma, to get the app to start automatically in IIS, you have to use Application Initialization. #46552160Wist
After killing a days time with the same issue on IIS v10 and ASP.NET Core 5 I've found the solution. In addition to the Application Initilization you have to set the Application Pool to Startmode=AlwaysRunning and the Site to Preload Enabled=true (both to find in the Advanced Settings). Also see https://mcmap.net/q/741701/-how-do-i-get-a-net-core-website-hosted-in-iis-to-start-immediatelyContrary
T
16

When you run this using Visual Studio, you are likely using IIS Express, which isn't going to run your ASP.NET Core project until the first request is made (that's really just how IIS works by default). This applies when using the InProcess hosting-model that's new with ASP.NET Core 2.2, which I expect you must be using in order to see this issue. See this GitHub issue for more.

You can prove this theory by removing the AspNetCoreHostingModel XML element from the .csproj file that you're using to host the ASP.NET Core application (which will switch it back to the OutOfProcess mode). It looks like there's a "Hosting Model" option under "Debug" in the project properties dialog of VS2017 that you can change to "Out Of Process" if you don't want to edit the .csproj directly.

If you want the hosting-model to be out-of-process only for a production site, you could use a Web.config transform, for example. If you want it to be out-of-process both during development and in production, just changing the property I called out above will be enough as this gets converted automatically into a Web.config property. If you would prefer to use the in-process model, enabling preload in the IIS application is a good option (described here).

Thurmond answered 25/1, 2019 at 11:4 Comment(1)
Using Out of Process does work when running from Visual Studio, but does not do anything when hosting in IIS. For IIS you have to use Application Initialization. #46552160Wist
V
2

Background services start when your application starts, then it's up to you to synchronize with it.

You can implement a background service by using the BackgroundService class from the namespace Microsoft.Extensions.Hosting(Microsoft.Extensions.Hosting.Abstractions assembly):

First the declare the interface of your service (in this case it is empty, not nice, but clean):

public interface IMyService : IHostedService
{
}

Then, declare your service. The following snippet declares a service that at startup waist for 5 seconds, and then executes a task every 2 minutes and half:

internal sealed class MyService : BackgroundService, IMyService
{
    private const int InitialDelay = 5 * 1000;  //5 seconds;
    private const int Delay = (5 * 60 * 1000) / 2; // 2.5 minutes

    private readonly ILogger<MyService> m_Logger;

    public MyService(ILogger<MyService> logger, IServiceProvider serviceProvider)
    {
        if (logger == null)
            throw new ArgumentNullException(nameof(logger));
        if (serviceProvider == null)
            throw new ArgumentNullException(nameof(serviceProvider));

        this.m_Logger = logger;
        this.m_ServiceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            m_Logger.LogDebug($"MyService is starting.");

            stoppingToken.Register(() => m_Logger.LogDebug($"MyService background task is stopping because cancelled."));

            if (!stoppingToken.IsCancellationRequested)
            {
                m_Logger.LogDebug($"MyService is waiting to be scheduled.");
                await Task.Delay(InitialDelay, stoppingToken);
            }

            m_Logger.LogDebug($"MyService is working.");

            while (!stoppingToken.IsCancellationRequested)
            {
                await DoSomethingAsync();

                await Task.Delay(Delay);
            }

            m_Logger.LogDebug($"MyService background task is stopping.");
        }
        catch (Exception ex)
        {
            m_Logger.LogDebug("MyService encountered a fatal error while w task is stopping: {Exception}.", ex.ToString());
        }
    }

    private async Task DoSomethingAsync()
    {
         // do something here
         await Task.Delay(1000);
    }
}

As you can see, it's up to you to keep the background service "alive". Finally, you have to register it in your Startup.cs at the end of your ConfigureServices method:

services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, MyService>();

This is sufficient to have the service started. keep in mind that your application could be actually started at a later time if hosted in IIS: your application is (re)started everytime your assembly is recycled. Instead, using Kestrel, provides a single instance application which will not be recycled.

For those using .Net Core 2.1 or lower, the Background class is not available, but you can get the definition from github (I post what I used in the past as the github repository can be moved):

//borrowed from .NET Core 2.1 (we are currently targeting 2.0.3)
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
    private Task _executingTask;

    private readonly CancellationTokenSource _stoppingCts =
                                                   new CancellationTokenSource();

    protected abstract Task ExecuteAsync(CancellationToken cancellationToken);

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // Store the task we're executing
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        // If the task is completed then return it,
        // this will bubble cancellation and failure to the caller
        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        // Otherwise it's running
        return Task.CompletedTask;
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executingTask == null)
        {
            return;
        }

        try
        {
            // Signal cancellation to the executing method
            _stoppingCts.Cancel();
        }
        finally
        {
            // Wait until the task completes or the stop token triggers
            await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
                                                          cancellationToken));
        }
    }

    public virtual void Dispose()
    {
        _stoppingCts.Cancel();
    }
}
Virescence answered 25/1, 2019 at 9:37 Comment(2)
How did you host it? I am facing trouble in deploying HostedService in IISConfederacy
I used kestrel and ngnix on linux. I haven't tried using IIS. IMHO Note that these kind of tasks are better suited when the service is not recycled: when appdomain recycling occurs (ie in IIS) you'll end up on firing these events more than usual and they could have unintended consequences.Virescence
G
0

If you want o have a Service doing background tasks (similar to old Windows Services) I would suggest you to use: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 instead of a WebHost.

WebHost add a lot of stuff that probably you won't need since seems a simple background job (assuming that reading your code).

Galosh answered 24/1, 2019 at 15:4 Comment(0)
E
0

For me... background tasks weren't starting until the first page request.

But then I noticed in my Publish / Edit, I didn't have Destination Url set. (and also I didn't have a home Index page)...

Once I added a valid Destination Url... that page would popup after publishing and be my "first" page request and background tasks would start.

Eutectoid answered 24/9, 2020 at 23:31 Comment(0)
D
-1

Hosted services do start when the host is starting. With the WebHost, the hosted services will be started right after the application has started. This means that if implemented correctly, your hosted service will run without requiring a request to come in.

When I try your example hosted service on a fresh ASP.NET Core application, it works just fine, so if it is not working for you, then apparently your actual implementation KontolerTimer is not correct.

Detinue answered 16/1, 2019 at 9:15 Comment(3)
Strange thing that I do not hit break point in first line ConfigureServices until I made first request. I did in visual studio tun off start web page in properties, but same happening when i deploy my app in IIS, There is no call of StarUp methods until first http request is madeHypervitaminosis
Yeah they definately don't when using iis express. Might get a different behaviour in iis or kestral directly.Monnet
How did you host it? I am facing trouble in deploying HostedService in IISConfederacy

© 2022 - 2024 — McMap. All rights reserved.