17

在我的 MVC .NET core 2.2 应用程序中有 HostedService 做后台工作。

在 Startap 类的 ConfigureServices 方法中注册

services.AddHostedService<Engines.KontolerTimer>();

由于这是独立于用户请求的后台服务,我想在应用程序启动时立即启动我的后台服务。现在是我的 HostedService 在第一个用户请求之后盯着的情况。

MVC Core 应用程序启动时启动 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();
    }
}

看起来我根本无法盯着应用程序。

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

而且在第一次用户请求之前我没有遇到任何断点。我错过了什么吗,这是 VS2017 创建的默认 .Net Core 应用程序

这是我的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

5 回答 5

15

当您使用 Visual Studio 运行它时,您可能会使用 IIS Express,它不会在发出第一个请求之前运行您的 ASP.NET Core 项目(这实际上就是 IIS 默认的工作方式)。这适用于使用 ASP.NET Core 2.2 中新增的 InProcess 托管模型,我希望您必须使用它才能看到此问题。有关更多信息,请参阅此GitHub 问题

您可以通过从用于托管 ASP.NET Core 应用程序的 .csproj 文件中删除 AspNetCoreHostingModel XML 元素来证明这一理论(这会将其切换回 OutOfProcess 模式)。看起来VS2017的项目属性对话框中的“调试”下有一个“托管模型”选项,如果您不想直接编辑.csproj,可以将其更改为“进程外”。

例如,如果您希望托管模型仅用于生产站点的进程外,则可以使用 Web.config 转换。如果您希望它在开发和生产期间都处于进程外,只需更改我上面提到的属性就足够了,因为它会自动转换为 Web.config 属性。如果您希望使用进程内模型,在 IIS 应用程序中启用预加载是一个不错的选择(在此处描述)。

于 2019-01-25T11:04:20.280 回答
2

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

您可以使用命名空间(程序集)中的BackgroundService类来实现后台服务:Microsoft.Extensions.HostingMicrosoft.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 DoSomethingAsync()
    {
         // 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();
    }
}
于 2019-01-25T09:37:49.470 回答
0

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

WebHost 添加了很多您可能不需要的东西,因为这似乎是一个简单的后台工作(假设阅读您的代码)。

于 2019-01-24T15:04:49.427 回答
0

对我来说......后台任务直到第一页请求才开始。

但后来我注意到在我的发布/编辑中,我没有设置目标网址。(而且我没有主页索引页面)......

一旦我添加了一个有效的目标网址......该页面将在发布后弹出并成为我的“第一个”页面请求,后台任务将开始。

于 2020-09-24T23:31:57.937 回答
-1

托管服务确实在主机启动时启动。使用 WebHost,托管服务将在应用程序启动后立即启动。这意味着如果实施得当,您的托管服务将无需请求即可运行。

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

于 2019-01-16T09:15:35.927 回答