如何让托管服务在 asp net core 3.1 中保持活动状态?
How to keep a hosted service alive in asp net core 3.1?
你好吗?
我在 net core 3.1 中有一个网络 api,这又包含一项服务,每 X 分钟必须 运行 执行数据迁移(我只是在测试它),但我有2个问题。
- 为了 运行 的服务,我必须首先 运行 我的 api 中的一些 url。问题是:如何让这项服务自动启动,而不需要 运行 任何 api?
- 当我停止使用 apis 几分钟后,服务停止工作。问题是:如何让服务 "Forever" 保持活动状态?
我必须强调,我的网站 api 托管在虚拟主机中,在那里我无法访问 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>();
});
}
继承自 BackgroundService
而不是实现 IHostedService
。这将负责为您启动、运行 和停止服务的机制。
但是您面临的问题是 IIS 直到服务的第一个请求才启动您的 c# 进程。如果没有请求,默认应用程序池设置将再次关闭它。我建议设置某种计划任务以定期请求 url 并监视服务是否为 运行。如果它无论如何都停止了,您会希望得到通知吗?
如果您从 BackgroundService
class 继承您的无限作业服务并在循环和所需的
中实现您的逻辑
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;
}
}
我想出的方法如下:
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 bootstrap 才能继续。这就是为什么我使用 Task.run 将实际工作转移到另一个线程,允许调用线程继续。
我很清楚这是一个相当古老的问题,并且已经有一些答案,但我想就此事提供我的意见。我使用了一个出色的库来安排名为 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.
}
}
如果你想运行每Xseconds/minutes/hours/days/etc一个方法。您将必须实施 IHostedService
: info and docs。然而,如果你只想执行一次该方法,你应该实现 BackgroundService
.
你好吗?
我在 net core 3.1 中有一个网络 api,这又包含一项服务,每 X 分钟必须 运行 执行数据迁移(我只是在测试它),但我有2个问题。
- 为了 运行 的服务,我必须首先 运行 我的 api 中的一些 url。问题是:如何让这项服务自动启动,而不需要 运行 任何 api?
- 当我停止使用 apis 几分钟后,服务停止工作。问题是:如何让服务 "Forever" 保持活动状态?
我必须强调,我的网站 api 托管在虚拟主机中,在那里我无法访问 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>();
});
}
继承自 BackgroundService
而不是实现 IHostedService
。这将负责为您启动、运行 和停止服务的机制。
但是您面临的问题是 IIS 直到服务的第一个请求才启动您的 c# 进程。如果没有请求,默认应用程序池设置将再次关闭它。我建议设置某种计划任务以定期请求 url 并监视服务是否为 运行。如果它无论如何都停止了,您会希望得到通知吗?
如果您从 BackgroundService
class 继承您的无限作业服务并在循环和所需的
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;
}
}
我想出的方法如下:
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 bootstrap 才能继续。这就是为什么我使用 Task.run 将实际工作转移到另一个线程,允许调用线程继续。
我很清楚这是一个相当古老的问题,并且已经有一些答案,但我想就此事提供我的意见。我使用了一个出色的库来安排名为 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.
}
}
如果你想运行每Xseconds/minutes/hours/days/etc一个方法。您将必须实施 IHostedService
: info and docs。然而,如果你只想执行一次该方法,你应该实现 BackgroundService
.