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

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

问题描述

我正在编写一个基于 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 core 有没有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
        }
    }
}

我现在不再循环true,而是循环判断令牌是否已取消.

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 core 中捕获退出信号?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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