ASP.. NET core似乎打破了默认的ctrl+c行为



我有一个用c#进行计算的过程。我通常在windows的控制台或Linux的docker容器中运行它。但我正在寻求一个一般的答案,我希望也适用于其他类型的应用程序,如windows服务或WPF应用程序。

我希望我的进程对应用程序关闭做出反应。我想保存所有的计算,让硬盘保持一致的状态,并在进程结束之前处理一些资源。

我以前是这样做的:

class Program
{
// longRunningTasks can be cancelled by CancellationToken from processExitTokenSource source 
static Task longRunningTasks;
static readonly CancellationTokenSource processExitTokenSource = new();
static async Task doTheMainJob(CancellationToken cancellationToken)
{
// the long running computations I am interested in
}
static void CurrentDomain_ProcessExit(object? sender, EventArgs e)
{
Console.WriteLine("I expect this line to be written to console when ctrl+c is pressed or when process is to be closed by other means.");
processExitTokenSource.Cancel();
// this waits for task to end
longRunningTasks.Wait();
// aplication should exit after this line is reached
}
static async Task Main()
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
var tasks = new List<Task>();
tasks.Add(doTheMainJob(cancellationToken))
if (Settings.AllowMonitoringThroughHttp) {
tasks.Add(WebServer.RunAsync(cancellationToken));
}
longRunningTasks = Task.WhenAll(tasks.ToArray());
await longRunningTasks;
}
}

它总是有效的。现在我想要更现代,我添加了简单的web服务器Microsoft.AspNetCore.Mvc server来监控进程。

class WebServer
{
public static async Task RunAsync(CancellationToken cancellationToken)
{
var builder = WebApplication.CreateBuilder(new string[0]);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
app.MapControllers();
await app.RunAsync(cancellationToken);
}
}

服务器监听一些端口,并完美地响应http请求。然而,它似乎以某种方式覆盖了ctrl+c行为和CurrentDomain_ProcessExit方法从未被调用。因此processExitTokenSource永远不会被取消,我的计算永远不会被取消。

hapens什么?如何配置web服务器使其正常运行?我想通过取消令牌来取消它

这个问题回答了如何禁用Ctrl+C键。

这里我们不想禁用密钥,而是将其行为恢复为默认值。我所能获得的最接近原始行为的方法是使用Console。CancelKeyPress,启动一个调用Environment.Exit(0)的任务。

Environment.Exit(0)不能从同一个线程调用,因为它会导致死锁。AppDomain.CurrentDomain.ProcessExit事件将等待WebApplication退出,WebApplication将等待Environment.Exit(0)结束。

public class NoopConsoleLifetime : IHostLifetime, IDisposable
{
private readonly ILogger<NoopConsoleLifetime> _logger;
public NoopConsoleLifetime(ILogger<NoopConsoleLifetime> logger)
{
_logger = logger;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("StopAsync was called");
return Task.CompletedTask;
}
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
// without this ctrl+c just kills the application
Console.CancelKeyPress += OnCancelKeyPressed;
return Task.CompletedTask;
}
private void OnCancelKeyPressed(object? sender, ConsoleCancelEventArgs e)
{
_logger.LogInformation("Ctrl+C has been pressed, ignoring.");
// e.Cancel = false would turn off the application without the clean up
e.Cancel = true;
// not running in task causes dead lock
// Environment.Exit calls the ProcessExit event which waits for web server which waits for this method to end
Task.Run(() => Environment.Exit(0));
}
public void Dispose()
{
Console.CancelKeyPress -= OnCancelKeyPressed;
}
}

将NoopConsoleLifeTime添加到依赖注入容器中。

builder.Services.AddSingleton<IHostLifetime, NoopConsoleLifetime>();

最新更新