![](/img/trans.png)
[英]Gracefully shutdown a generic host in .NET Core 2 linux daemon
[英]Graceful shutdown with Generic Host in .NET Core 2.1
.NET Core 2.1 引入了新的通用主机,它允许托管非 HTTP 工作负载,并具有 Web 主机的所有优势。 目前,没有太多的信息和食谱,但我使用了以下文章作为起点:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.1
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
我的 .NET Core 应用程序启动,通过 RabbitMQ 消息代理侦听新请求,并根据用户请求(通常在控制台中按 Ctrl+C)关闭。 但是,关闭并不优雅 - 应用程序在将控制权返回给操作系统时仍有未完成的后台线程。 我通过控制台消息看到它 - 当我在控制台中按 Ctrl+C 时,我看到我的应用程序的几行控制台输出,然后是操作系统命令提示符,然后是我的应用程序的控制台输出。
这是我的代码:
程序.cs
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.SetBasePath(AppContext.BaseDirectory);
config.AddEnvironmentVariables(prefix: "ASPNETCORE_");
config.AddJsonFile("hostsettings.json", optional: true);
})
.ConfigureAppConfiguration((context, config) =>
{
var env = context.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsProduction())
config.AddDockerSecrets();
config.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
services.AddLogging();
services.AddHostedService<WorkerPoolHostedService>();
// ... other services
})
.ConfigureLogging((context, logging) =>
{
if (context.HostingEnvironment.IsDevelopment())
logging.AddDebug();
logging.AddSerilog(dispose: true);
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(context.Configuration)
.CreateLogger();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
}
}
WorkerPoolHostedService.cs
internal class WorkerPoolHostedService : IHostedService
{
private IList<VideoProcessingWorker> _workers;
private CancellationTokenSource _stoppingCts = new CancellationTokenSource();
protected WorkerPoolConfiguration WorkerPoolConfiguration { get; }
protected RabbitMqConfiguration RabbitMqConfiguration { get; }
protected IServiceProvider ServiceProvider { get; }
protected ILogger<WorkerPoolHostedService> Logger { get; }
public WorkerPoolHostedService(
IConfiguration configuration,
IServiceProvider serviceProvider,
ILogger<WorkerPoolHostedService> logger)
{
this.WorkerPoolConfiguration = new WorkerPoolConfiguration(configuration);
this.RabbitMqConfiguration = new RabbitMqConfiguration(configuration);
this.ServiceProvider = serviceProvider;
this.Logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var connectionFactory = new ConnectionFactory
{
AutomaticRecoveryEnabled = true,
UserName = this.RabbitMqConfiguration.Username,
Password = this.RabbitMqConfiguration.Password,
HostName = this.RabbitMqConfiguration.Hostname,
Port = this.RabbitMqConfiguration.Port,
VirtualHost = this.RabbitMqConfiguration.VirtualHost
};
_workers = Enumerable.Range(0, this.WorkerPoolConfiguration.WorkerCount)
.Select(i => new VideoProcessingWorker(
connectionFactory: connectionFactory,
serviceScopeFactory: this.ServiceProvider.GetRequiredService<IServiceScopeFactory>(),
logger: this.ServiceProvider.GetRequiredService<ILogger<VideoProcessingWorker>>(),
cancellationToken: _stoppingCts.Token))
.ToList();
this.Logger.LogInformation("Worker pool started with {0} workers.", this.WorkerPoolConfiguration.WorkerCount);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
this.Logger.LogInformation("Stopping working pool...");
try
{
_stoppingCts.Cancel();
await Task.WhenAll(_workers.SelectMany(w => w.ActiveTasks).ToArray());
}
catch (AggregateException ae)
{
ae.Handle((Exception exc) =>
{
this.Logger.LogError(exc, "Error while cancelling workers");
return true;
});
}
finally
{
if (_workers != null)
{
foreach (var worker in _workers)
worker.Dispose();
_workers = null;
}
}
}
}
VideoProcessingWorker.cs
internal class VideoProcessingWorker : IDisposable
{
private readonly Guid _id = Guid.NewGuid();
private bool _disposed = false;
protected IConnection Connection { get; }
protected IModel Channel { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected ILogger<VideoProcessingWorker> Logger { get; }
protected CancellationToken CancellationToken { get; }
public VideoProcessingWorker(
IConnectionFactory connectionFactory,
IServiceScopeFactory serviceScopeFactory,
ILogger<VideoProcessingWorker> logger,
CancellationToken cancellationToken)
{
this.Connection = connectionFactory.CreateConnection();
this.Channel = this.Connection.CreateModel();
this.Channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
this.ServiceScopeFactory = serviceScopeFactory;
this.Logger = logger;
this.CancellationToken = cancellationToken;
#region [ Declare ]
// ...
#endregion
#region [ Consume ]
// ...
#endregion
}
// ... worker logic ...
public void Dispose()
{
if (!_disposed)
{
this.Channel.Close(200, "Goodbye");
this.Channel.Dispose();
this.Connection.Close();
this.Connection.Dispose();
this.Logger.LogDebug("Worker {0}: disposed.", _id);
}
_disposed = true;
}
}
因此,当我按 Ctrl+CI 时,在控制台中看到以下输出(当没有请求处理时):
正在停止工作池...
命令提示符
工人ID :已处理。
如何优雅地关机?
你需要IApplicationLifetime
。 这为您提供了有关应用程序启动和关闭的所有所需信息。 您甚至可以通过appLifetime.StopApplication();
触发关闭appLifetime.StopApplication();
片段(如果链接无效):
public Task StartAsync(CancellationToken cancellationToken)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
我将分享一些我认为非常适用于非 WebHost 项目的模式。
namespace MyNamespace
{
public class MyService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly IApplicationLifetime _appLifetime;
public MyService(
IServiceProvider serviceProvider,
IApplicationLifetime appLifetime)
{
_serviceProvider = serviceProvider;
_appLifetime = appLifetime;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_appLifetime.ApplicationStopped.Register(OnStopped);
return RunAsync(stoppingToken);
}
private async Task RunAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
using (var scope = _serviceProvider.CreateScope())
{
var runner = scope.ServiceProvider.GetRequiredService<IMyJobRunner>();
await runner.RunAsync();
}
}
}
public void OnStopped()
{
Log.Information("Window will close automatically in 20 seconds.");
Task.Delay(20000).GetAwaiter().GetResult();
}
}
}
关于这个类的一些注意事项:
在 Main( string[] args ) 中设置主机,以便在调用 CTRL+C / SIGTERM 时正常关闭:
IHost host = new HostBuilder()
.ConfigureServices( ( hostContext, services ) =>
{
services.AddHostedService<MyService>();
})
.UseConsoleLifetime()
.Build();
host.Run(); // use RunAsync() if you have access to async Main()
我发现这组模式在 ASP.NET 应用程序之外工作得很好。
请注意,Microsoft 已针对 .NET Standard 进行构建,因此您无需使用 .NET Core 即可利用这些新的便利。 如果您在 Framework 中工作,只需添加相关的 NuGet 包。 该包是针对 .NET Standard 2.0 构建的,因此您需要使用 Framework 4.6.1 或更高版本。 您可以在此处找到所有基础设施的代码,并随意浏览您正在使用的所有抽象的实现: https : //github.com/aspnet/Extensions
对于.NET Core 3.0,您可以注入并使用IHostApplicationLifetime
:
private readonly IHostApplicationLifetime _applicationLifetime;
public async Task StartAsync(CancellationToken cancellationToken)
{
await DoWork();
_applicationLifetime.StopApplication();
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("StopAsync will be called after call to StopApplication");
return Task.CompletedTask;
}
在Startup.cs
,您可以使用当前进程的Kill()
方法终止应用程序:
public void Configure(IHostApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(() =>
{
Console.WriteLine("Press Ctrl+C to shut down.");
});
appLifetime.ApplicationStopped.Register(() =>
{
Console.WriteLine("Shutting down...");
System.Diagnostics.Process.GetCurrentProcess().Kill();
});
}
程序.cs
在构建主机时不要忘记使用UseConsoleLifetime()
。
Host.CreateDefaultBuilder(args).UseConsoleLifetime(opts => opts.SuppressStatusMessages = true);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.