代码之家  ›  专栏  ›  技术社区  ›  adopilot

如何在MVC核心应用程序中无HTTP请求启动HostedService

  •  5
  • adopilot  · 技术社区  · 6 年前

    在我的MVC.NET核心2.2应用程序中,有一个hostedService负责后台工作。

    它在startap类的configureServices方法中注册

    services.AddHostedService<Engines.KontolerTimer>();
    

    因为这是独立于用户请求的后台服务,所以我希望在应用启动时立即启动后台服务。 现在是我的hostedService在第一个用户请求后开始的情况。

    当MVC核心应用程序启动时,启动HostedService的正确方法是什么

    我的系列看起来像这个 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();
        }
    }
    

    看起来我盯着这个应用有问题。

    我的电影C看起来像

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

    在第一个用户请求之前,我不会碰到任何断点。 我错过了什么吗?这是VS2017创建的默认.NET核心应用程序

    这是我的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 回复  |  直到 6 年前
        1
  •  2
  •   Kirk Larkin    6 年前

    使用Visual Studio运行时,很可能会使用IIS Express,在发出第一个请求之前,它不会运行ASP.NET核心项目(这实际上只是默认情况下IIS的工作方式)。这适用于使用ASP.NET Core 2.2新的进程内宿主模型时,我希望您必须使用该模型才能看到此问题。看看这个 GitHub issue 更多。

    您可以通过从.csproj文件中删除aspnetcorehostingmodel xml元素来证明此理论,该文件正用于宿主ASP.NET核心应用程序(它将切换回outofprocess模式)。看起来在vs2017的项目属性对话框中的“debug”下有一个“hosting model”选项,如果不想直接编辑.csproj,可以改为“out of process”。

    例如,如果您希望仅生产站点的宿主模型不在处理过程中,则可以使用web.config转换。如果您希望它在开发和生产过程中都不在进程中,只要更改上面调用的属性就足够了,因为它会自动转换为web.config属性。如果您希望使用进程内模型,那么在IIS应用程序中启用预加载是一个很好的选择(已描述 here )

        2
  •  0
  •   poke    6 年前

    托管服务 主机启动时启动。通过WebHost,托管服务 will be started right after the application has started . 这意味着,如果实现正确,托管服务将在不需要请求进入的情况下运行。

    当我在一个新的ASP.NET核心应用程序上尝试您的示例托管服务时,它工作得很好,所以如果它不适合您,那么显然您的 实际的 实施 KontolerTimer 是不对的。

        3
  •  0
  •   gatsby    6 年前

    如果要让服务执行后台任务(类似于旧的Windows服务),我建议您使用: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 而不是网络主机。

    webhost添加了很多你可能不需要的东西,因为这看起来是一个简单的后台工作(假设你在读代码)。

        4
  •  0
  •   Yennefer    6 年前

    后台服务在应用程序启动时启动,然后由您来与之同步。

    您可以使用 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或更低版本的用户,后台类不可用,但您可以从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();
        }
    }
    
    推荐文章