如何在Linux的asp.net核心中捕获退出信号? [英] how to catch exit signal in asp.net core on linux?
问题描述
我正在基于net core 3.1 linux编写一个c#控制台应用程序
I am writing a c# console app base on net core 3.1 linux
预计会
- 异步运行作业
- 等待工作结束
- 捕获到终止信号并做一些干净的工作
这是我的演示代码:
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监视的kill信号?
does the async/await block the kill signal to be watched by eventHandler?
意外的异常eventHandler也没有任何输出.
the unexpected exception eventHandler do not have any output too.
asp.net核心中是否有任何 signal.signal(signal.SIGTERM,func)
?
is there any signal.signal(signal.SIGTERM, func)
in asp.net core?
推荐答案
好吧,这可能有点长,但这确实可行.
Ok, this may be a tad long winded, but here it goes.
这里的主要问题是 HttpListener.GetContextAsync()
不支持通过 CancellationToken
取消.因此,很难以某种优雅的方式取消此操作.我们需要做的是伪造"商品.取消.
The main issue here is HttpListener.GetContextAsync()
does not support cancellation via CancellationToken
. So it's tough to cancel this operation in a somewhat graceful manner. What we need to do is "fake" a cancellation.
Stephen Toub是 async
/ await
模式的大师.对我们来说幸运的是,他写了一篇题为"如何取消不可取消的异步操作?"的文章.您可以在此处进行检查>.
Stephen Toub is a master in the async
/await
pattern. Luckily for us he wrote an article entitled How do I cancel non-cancelable async operations?. You can check it out here.
我不相信使用 AppDomain.CurrentDomain.ProcessExit
事件.您可以阅读有关为什么有些人试图避免使用它的信息.
I don't believe in using the AppDomain.CurrentDomain.ProcessExit
event. You can read up on why some folks try to avoid it.
我将使用控制台.CancelKeyPress 事件.
因此,在程序文件中,我将其设置如下:
So, in the program file, I have set it up like this:
Program.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()
方法以接受来自该来源的令牌:
I took your code and added the Console.CancelKeyPress
event and added a CancellationTokenSource
. I also modified your SimpleHttpServer.RunAsync()
method to accept a token from that source:
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
}
}
}
我现在循环查看令牌是否被标记为已取消.
Instead of looping on true
, I now loop on the whether or not the token is signaled as cancelled or not.
与此有关的另一件事很奇怪,就是在 _httpListener.GetContextAsync()
行中添加了 WithCancellation
方法.
Another thing that is quite odd about this is the addition of WithCancellation
method to the _httpListener.GetContextAsync()
line.
此代码来自上面的Stephen Toub文章.我创建了一个新文件,用于保存任务的扩展名:
This code is from the Stephen Toub article above. I created a new file that is meant to hold extensions for tasks:
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;
}
}
由于上面的文章对此进行了很好的解释,因此我不会对其进行详细介绍.
I won't go in to much detail about how it works because the article above explains it just fine.
现在,当您捕获CTRL + C信号时,该令牌会发出信号取消信号,该信号将引发 OperationCanceledException
,从而中断该循环.我们抓到它,扔到一边,然后退出.
Now, when you catch the CTRL+C signal, the token is signaled to cancel which will throw a OperationCanceledException
which breaks that loop. We catch it and toss it aside and exit.
如果您想继续使用 AppDomain.CurrentDomain.ProcessExit
,则可以-您的选择..只需将 Console.CancelKeyPress
内的代码添加到其中即可事件.
If you want to continue to use AppDomain.CurrentDomain.ProcessExit
, you can -- your choice.. just add the code inside of Console.CancelKeyPress
in to that event.
然后该程序将正常退出...尽可能优雅地退出.
The program will then exit gracefully... well, as gracefully as it can.
这篇关于如何在Linux的asp.net核心中捕获退出信号?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!