问题
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