首页 > 解决方案 > 如何在 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核心中有没有?

标签: c#.net-coreasync-await

解决方案


好的,这可能有点啰嗦,但它就是这样。

这里的主要问题是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.CancelKeyPressin 中的代码添加到该事件中即可。

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


推荐阅读