how to catch exit signal in asp.net core on linux?

怎甘沉沦 提交于 2021-01-28 10:35:02

问题


I am writing a c# console app base on net core 3.1 linux

It was expected to

  • run job async
  • await job end
  • catch the kill signal and do some clean job

here is my demo code:


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();
            }
        }
    }
}

expect it will print

Now in  while (true)
Now after http.RunAsync();
ProcessExit!
ProcessExit! finished

but it only output

$ dotnet run
Now in  while (true)
Now after http.RunAsync();
^C%

does the async/await block the kill signal to be watched by eventHandler?

the unexpected exception eventHandler do not have any output too.

is there any signal.signal(signal.SIGTERM, func) in asp.net core?


回答1:


Ok, this may be a tad long winded, but here it goes.

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 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.

I don't believe in using the AppDomain.CurrentDomain.ProcessExit event. You can read up on why some folks try to avoid it.

I will use the Console.CancelKeyPress event though.

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");
    }
}

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.

Another thing that is quite odd about this is the addition of WithCancellation method to the _httpListener.GetContextAsync() line.

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.

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.

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.



来源:https://stackoverflow.com/questions/63249524/how-to-catch-exit-signal-in-asp-net-core-on-linux

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!