如何在ASP.NET Core 3.1中保持托管服务的运行?

7

你好。

我有一个基于Net Core 3.1的Web API,其中包含一个服务,每隔X分钟就要运行一次数据迁移(我只是在测试它),但是我有两个问题。

  1. 为了让服务运行,我必须先运行我的API的某些URL。问题是:如何使这个服务自动启动,而不需要运行任何API?
  2. 当我停止使用API几分钟后,服务就停止工作了。问题是:如何让服务“永久”保持运行状态?

我必须强调,我的Web API托管在Web托管中,我无法访问所有IIS功能。

这是我的代码,提前感谢你的帮助。

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

你应该切换到工作服务,https://devblogs.microsoft.com/aspnet/net-core-workers-as-windows-services/ 并将其单独部署为Windows服务。 - Lex Li
4个回答

5

与其实现IHostedService接口,不如从BackgroundService类中继承。这会为您处理启动、运行和停止服务的机制。

但是您面临的问题是IIS在服务的第一个请求之前不会启动c#进程。然后,如果没有请求,默认的应用程序池设置将再次关闭它。我建议设置某种类型的定期请求 URL 的计划任务,并监控该服务是否正在运行。无论如何,如果它停止了,您都需要通知吧?


1
我想到的方法如下所示:

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

重要的是:StartAsync必须尽快退出,以便IGenericHost引导程序可以继续。这就是为什么我使用Task.run将真正的工作转移到另一个线程中,允许调用线程继续。

0

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

如果您从BackgroundService类继承您的无限作业服务并在循环内实现逻辑并需要…
await Task.Delay(TimeSpan.FromMinutes(x miutes))

该任务将在应用程序启动时立即运行,无需任何API调用,并在应用程序停止时停止。

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

4
为什么要继承自 BackgroundService,然后重写实际使其工作的两个方法? - Jeremy Lakeman
2
这是错误的方法。由于 IGenericHost 的设计(托管服务在服务器 Kestrel 启动之前启动),StartAsync 必须返回。StartAsync 必须尽快返回(返回 Task.CompletedTask)。您可以通过注入 IHostApplicationLifetime 来验证此操作,您会发现在关闭应用程序后 OnStarted 事件仍会触发。 - Mitja Gustin

-3

我知道这是一个相当古老的问题,已经有了一些答案,但我想提供我的看法。我使用了一个名为FluentScheduler的出色库来安排任务。在开始之前,请将其添加到您的Startup.cs中:

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

这是我解决上述问题的方法:
    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.
        }
    }

如果您想每隔X秒/分钟/小时/天等运行一个方法,您将需要实现 IHostedServiceinfodocs。然而,如果您只想执行该方法一次,您应该实现 BackgroundService

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接