.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)关闭。但是,关机并不是正常的-应用程序在将控制权返回给OS时仍具有未完成的后台线程。我通过控制台消息看到它-当我在控制台中按Ctrl + C时,我从应用程序中看到几行控制台输出,然后是OS命令提示符,然后又是我的应用程序中的控制台输出。
这是我的代码:
Program.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 + C时,我会在控制台中看到以下输出(没有请求处理时):
正在停止工作池...
命令提示符
工人 id :已处置。
如何正常关闭?
答案 0 :(得分:7)
您需要IApplicationLifetime
。这为您提供了有关应用程序启动和关闭的所有必需信息。您甚至可以通过appLifetime.StopApplication();
代码段(如果链接无效):
public Task StartAsync(CancellationToken cancellationToken)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
答案 1 :(得分:1)
我将分享一些我认为适用于非WebHost项目的模式。
namespace MyNamespace
{
public class MyService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly IApplicationLifetime _appLifetime;
public QbdSkuVaultIntSchedulerService(
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.2构建的,因此您需要使用Framework 4.6.1或更高版本。您可以在此处找到所有基础结构的代码,并随时查看正在使用的所有抽象的实现:https://github.com/aspnet/Extensions
答案 2 :(得分:0)
在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();
});
}
Program.cs
在构建主机时不要忘记使用UseConsoleLifetime()
。
Host.CreateDefaultBuilder(args).UseConsoleLifetime(opts => opts.SuppressStatusMessages = true);