Как запустить HostedService в приложении MVC Core без http-запроса

В моем приложении MVC .NET core 2.2 есть HostedService, который выполняет фоновую работу.

Регистрируется в методе ConfigureServices класса Startap

services.AddHostedService<Engines.KontolerTimer>();

Так как это фоновая служба, независимая от запросов пользователей, я хочу сразу же запустить мою фоновую службу при запуске приложения. Теперь дело за моим HostedService, начиная с первого запроса пользователя.

Как правильно запускать HostedService при запуске приложения MVC Core?

Моя служба выглядит так: https://docs.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();
    }
}

Похоже, у меня проблема с запуском приложения вообще.

Моя порция cs выглядит так

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>();
    }

И я не бью ни одной точки останова перед первым запросом пользователя. Я что-то пропустил, это приложение по умолчанию.Net Core, созданное VS2017

Вот мой 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>();}

4 ответа

Решение

Когда вы запускаете это с помощью Visual Studio, вы, скорее всего, используете IIS Express, который не будет запускать ваш проект ASP.NET Core до тех пор, пока не будет сделан первый запрос (на самом деле IIS работает по умолчанию). Это применимо при использовании хостинг-модели InProcess, которая впервые появилась в ASP.NET Core 2.2, и я ожидаю, что вы должны использовать ее, чтобы увидеть эту проблему. Смотрите эту проблему GitHub для получения дополнительной информации.

Вы можете доказать эту теорию, удалив XML-элемент AspNetCoreHostingModel из файла.csproj, который вы используете для размещения основного приложения ASP.NET (которое переключит его обратно в режим OutOfProcess). Похоже, что есть опция "Модель хостинга" в разделе "Отладка" в диалоговом окне свойств проекта VS2017, которую вы можете изменить на "Вне процесса", если вы не хотите редактировать.csproj напрямую.

Если вы хотите, чтобы хостинг-модель была вне процесса только для рабочего сайта, вы можете использовать, например, преобразование Web.config. Если вы хотите, чтобы он был вне процесса как во время разработки, так и в производстве, достаточно просто изменить свойство, которое я назвал выше, поскольку оно автоматически преобразуется в свойство Web.config. Если вы предпочитаете использовать внутрипроцессную модель, хорошим вариантом будет включение предварительной загрузки в приложении IIS (описано здесь).

Фоновые службы запускаются при запуске приложения, а затем вам нужно синхронизироваться с ним.

Вы можете реализовать сервис backhround, используя BackgroundService класс из пространства имен Microsoft.Extensions.Hosting(Microsoft.Extensions.Hosting.Abstractions монтаж):

Сначала объявите интерфейс вашего сервиса (в этом случае он пустой, не красивый, но чистый):

public interface IMyService : IHostedService
{
}

Затем объявите свой сервис. Следующий фрагмент объявляет службу, которая запускается при запуске в течение 5 секунд, а затем выполняет задачу каждые 2 с половиной минуты:

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 DoSomrthingAsync()
    {
         // do something here
         await Task.Delay(1000);
    }

}

Как видите, вы должны поддерживать фоновую службу "живой". Наконец, вы должны зарегистрировать его в своем Startup.cs в конце вашего ConfigureServices метод:

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

Этого достаточно для запуска службы. имейте в виду, что ваше приложение может быть действительно запущено позднее, если оно размещено в IIS: ваше приложение (пере) запускается каждый раз, когда ваша сборка перерабатывается. Вместо этого, используя Kestrel, предоставляет единственное приложение, которое не будет переработано.

Для тех, кто использует.Net Core 2.1 или ниже, класс Background недоступен, но вы можете получить определение от github (я публикую то, что я использовал в прошлом, так как репозиторий github можно перемещать):

//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();
    }
}

Если вы хотите, чтобы Служба выполняла фоновые задачи (аналогично старым Службам Windows), я бы предложил вам использовать: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 вместо WebHost.

WebHost добавляет много вещей, которые, вероятно, вам не понадобятся, так как кажется простой фоновой работой (при условии, что вы читаете ваш код).

Для меня... фоновые задачи не запускались до запроса первой страницы.

Но затем я заметил в моей публикации / редактировании, что у меня не установлен целевой URL. (а также у меня не было домашней индексной страницы)...

Как только я добавлю действующий URL-адрес назначения... эта страница появится после публикации и станет моим "первым" запросом страницы, и начнутся фоновые задачи.

Хостинговые сервисы запускаются при запуске хоста. С помощью WebHost размещенные службы будут запущены сразу после запуска приложения. Это означает, что при правильной реализации ваша размещенная служба будет работать без запроса на вход.

Когда я пробую ваш пример размещенной службы на новом приложении ASP.NET Core, он работает просто отлично, поэтому, если он не работает для вас, то, очевидно, ваша реальная реализация KontolerTimer не является правильным.

Другие вопросы по тегам