c# - 如何在 Linux 上的 asp.net 核心中捕获退出信号?
问题描述
我正在编写基于 net core 3.1 linux 的 ac# 控制台应用程序
预计将
- 异步运行作业
- 等待工作结束
- 捕捉杀死信号并做一些干净的工作
这是我的演示代码:
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 也没有任何输出。
signal.signal(signal.SIGTERM, func)
asp.net核心中有没有?
解决方案
好的,这可能有点啰嗦,但它就是这样。
这里的主要问题是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()
方法以接受来自该来源的令牌:
简单的HttpServer.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
}
}
}
现在,我不再循环 on ,而是循环true
标记令牌是否被标记为已取消。
另一件很奇怪的事情是在WithCancellation
该行中添加了方法_httpListener.GetContextAsync()
。
此代码来自上面的 Stephen Toub 文章。我创建了一个用于保存任务扩展的新文件:
任务扩展.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
in 中的代码添加到该事件中即可。
然后程序将优雅地退出......好吧,尽可能优雅地退出。
推荐阅读
- flutter - Dart 将动态转换为设置
- tomcat8 - 无法在 ubuntu 18.04 上为 https 配置 tomcat 8.5.57 网络服务器
- mongodb - 强制猫鼬默认
- python - 从 sqlite3 获取数据并保存在列表中
- html - 有什么方法可以将引导样式添加到 Django 密码输入字段?
- r - 防止 Rcpp 在 RcppExports.R 中为非导出函数创建包装器
- youtube-data-api - 域更改 YouTube 数据 API v3 不起作用
- javascript - Vue.js 应用程序在 Chrome 中显示正常,但在 Firefox 中显示空白页 - __webpack_require__ 错误
- python - IndentationError: unindent 不匹配任何外部缩进级别 (Elif)
- javascript - Promise 返回 undefined - 在 Promise 中循环