如何在linux上捕捉asp.net内核中的退出信号



我正在写一个基于net core 3.1 linux 的c#控制台应用程序

预计

  • 异步运行作业
  • 等待作业结束
  • 抓住杀人信号,做一些干净的工作

这是我的演示代码:


namespace DeveloperHelper
{
public class Program
{
public static async Task Main(string[] args)
{
var http = new SimpleHttpServer();
var t = http.RunAsync();
Console.WriteLine("Now after http.RunAsync();");
AppDomain.CurrentDomain.UnhandledException += (s, e) => {
var ex = (Exception)e.ExceptionObject;
Console.WriteLine(ex.ToString());
Environment.Exit(System.Runtime.InteropServices.Marshal.GetHRForException(ex));
};
AppDomain.CurrentDomain.ProcessExit +=  async (s, e) =>
{
Console.WriteLine("ProcessExit!");
await Task.Delay(new TimeSpan(0,0,1));
Console.WriteLine("ProcessExit! finished");
};
await Task.WhenAll(t);
}
}
public class SimpleHttpServer
{
private readonly HttpListener _httpListener;
public SimpleHttpServer()
{
_httpListener = new HttpListener();
_httpListener.Prefixes.Add("http://127.0.0.1:5100/");
}
public async Task RunAsync()
{
_httpListener.Start();
while (true)
{
Console.WriteLine("Now in  while (true)");
var context = await _httpListener.GetContextAsync();
var response = context.Response;
const string rc = "{"statusCode":200, "data": true}";
var rbs = Encoding.UTF8.GetBytes(rc);
var st = response.OutputStream;
response.ContentType = "application/json";
response.StatusCode = 200;
await st.WriteAsync(rbs, 0, rbs.Length);
context.Response.Close();
}
}
}
}

预计它将打印

Now in  while (true)
Now after http.RunAsync();
ProcessExit!
ProcessExit! finished

但它只输出

$ dotnet run
Now in  while (true)
Now after http.RunAsync();
^C%

async/await是否阻止eventHandler监视的终止信号?

意外的异常eventHandler也没有任何输出。

asp.net核心中有signal.signal(signal.SIGTERM, func)吗?

好吧,这可能有点冗长,但到此为止。

这里的主要问题是HttpListener.GetContextAsync()不支持通过CancellationToken取消。因此,很难以一种优雅的方式取消这次手术。我们需要做的是";假的";取消。

Stephen Toub是async/await模式中的大师。幸运的是,他写了一篇题为如何取消不可取消的异步操作。你可以在这里查看。

我不相信使用AppDomain.CurrentDomain.ProcessExit事件。你可以阅读为什么有些人试图避免它。

不过,我将使用Console.CancelKeyPress事件。

因此,在程序文件中,我将其设置为:

程序.cs

class Program
{
private static readonly CancellationTokenSource _cancellationToken =
new CancellationTokenSource();
static async Task Main(string[] args)
{
var http = new SimpleHttpServer();
var taskRunHttpServer = http.RunAsync(_cancellationToken.Token);
Console.WriteLine("Now after http.RunAsync();");
Console.CancelKeyPress += (s, e) =>
{
_cancellationToken.Cancel();
};
await taskRunHttpServer;
Console.WriteLine("Program end");
}
}

我获取了您的代码并添加了Console.CancelKeyPress事件,并添加了一个CancellationTokenSource。我还修改了您的SimpleHttpServer.RunAsync()方法,以接受来自以下来源的令牌:

SimpleHttpServer.cs

public class SimpleHttpServer
{
private readonly HttpListener _httpListener;
public SimpleHttpServer()
{
_httpListener = new HttpListener();
_httpListener.Prefixes.Add("http://127.0.0.1:5100/");
}
public async Task RunAsync(CancellationToken token)
{
try
{
_httpListener.Start();
while (!token.IsCancellationRequested)
{
// ...
var context = await _httpListener.GetContextAsync().
WithCancellation(token);
var response = context.Response;
// ...
}
}
catch(OperationCanceledException)
{
// we are going to ignore this and exit gracefully
}
}
}

我现在不再在true上循环,而是在令牌是否被信号通知为已取消上循环。

另一件很奇怪的事情是将WithCancellation方法添加到_httpListener.GetContextAsync()行。

这个代码来自上面Stephen Toub的文章。我创建了一个新文件,用来保存任务的扩展名:

TaskExtensions.cs

public static class TaskExtensions
{
public static async Task<T> WithCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
}

我不会详细介绍它是如何工作的,因为上面的文章很好地解释了它。

现在,当您捕捉到CTRL+C信号时,令牌会被通知取消,这将抛出一个OperationCanceledException,从而中断该循环。我们抓住它,把它扔到一边,然后离开。

如果你想继续使用AppDomain.CurrentDomain.ProcessExit,你可以——你的选择。。只需将Console.CancelKeyPress内部的代码添加到该事件中即可。

然后程序将优雅地退出。。。好吧,尽可能优雅。

最新更新