且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在 linux 上的 asp.net core 中捕获退出信号?

更新时间:2021-08-27 00:32:34

好吧,这可能有点啰嗦,但就是这样.

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.