如何在Linux的asp.net核心中捕获退出信号? [英] how to catch exit signal in asp.net core on linux?

查看:56
本文介绍了如何在Linux的asp.net核心中捕获退出信号?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在基于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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆