C# 如何在linux上的asp.net内核中捕获退出信号?

C# 如何在linux上的asp.net内核中捕获退出信号?,c#,.net-core,async-await,C#,.net Core,Async Await,我正在写一个基于NetCore3.1Linux的c#控制台应用程序 预计会有 异步运行作业 等待工作结束 抓住杀戮信号,干净利落 这是我的演示代码: 命名空间DeveloperHelper { 公共课程 { 公共静态异步任务主(字符串[]args) { var http=new SimpleHttpServer(); var t=http.RunAsync(); WriteLine(“现在在http.RunAsync()之后;”; AppDomain.CurrentDomain.Unhan

我正在写一个基于NetCore3.1Linux的c#控制台应用程序

预计会有

  • 异步运行作业
  • 等待工作结束
  • 抓住杀戮信号,干净利落
这是我的演示代码:


命名空间DeveloperHelper
{
公共课程
{
公共静态异步任务主(字符串[]args)
{
var http=new SimpleHttpServer();
var t=http.RunAsync();
WriteLine(“现在在http.RunAsync()之后;”;
AppDomain.CurrentDomain.UnhandledException+=(s,e)=>{
var ex=(Exception)e.ExceptionObject;
Console.WriteLine(例如ToString());
Exit(System.Runtime.InteropServices.Marshal.GetHRForException(ex));
};
AppDomain.CurrentDomain.ProcessExit+=异步(s,e)=>
{
Console.WriteLine(“ProcessExit!”);
等待任务延迟(新的时间跨度(0,0,1));
Console.WriteLine(“ProcessExit!finished”);
};
等待任务。WhenAll(t);
}
}
公共类SimpleHttpServer
{
私有只读HttpListener\u HttpListener;
公共SimpleHttpServer()
{
_httpListener=新的httpListener();
_httpListener.Prefixes.Add(“http://127.0.0.1:5100/");
}
公共异步任务RunAsync()
{
_httpListener.Start();
while(true)
{
Console.WriteLine(“现在在while(true)”;
var context=awaitu httpListener.GetContextAsync();
var-response=context.response;
常量字符串rc=“{\'statusCode\':200,\'data\':true}”;
var rbs=Encoding.UTF8.GetBytes(rc);
var st=response.OutputStream;
response.ContentType=“application/json”;
response.StatusCode=200;
wait st.WriteAsync(rbs,0,rbs.Length);
context.Response.Close();
}
}
}
}
希望它能打印出来

Now in  while (true)
Now after http.RunAsync();
ProcessExit!
ProcessExit! finished
但它只是输出

$dotnet运行
时过境迁(真实)
现在在http.RunAsync()之后;
^C%
async/await是否阻止eventHandler监视的终止信号

意外异常eventHandler也没有任何输出


asp.net核心中是否有任何
signal.signal(signal.SIGTERM,func)

好的,这可能有点冗长,但现在开始

这里的主要问题是
HttpListener。GetContextAsync()
不支持通过
CancellationToken
取消。所以很难以一种优雅的方式取消这个操作。我们需要做的是“伪造”取消

Stephen Toub是
异步
/
等待
模式的大师。幸运的是,他写了一篇文章,题为“如何取消不可取消的异步操作?”?。你可以去看看

我不相信使用AppDomain.CurrentDomain.ProcessExit事件。你可以读到为什么有些人试图避免它

不过我会使用这个事件

因此,在程序文件中,我将其设置为:

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");
    }
}
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
        }
    }
}
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;
    }
}
我获取了您的代码并添加了
控制台。CancelKeyPress
事件并添加了
CancellationTokenSource
。我还修改了您的
SimpleHttpServer.RunAsync()
方法以接受来自该源的令牌:

SimpleHttpServer.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");
    }
}
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
        }
    }
}
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;
    }
}
现在,我不再在
true
上循环,而是在标记是否被取消的信号上循环

另一件非常奇怪的事情是,在httpListener.GetContextAsync()行中添加了
with cancellation
方法

这段代码来自上面的Stephen Toub文章。我创建了一个用于保存任务扩展名的新文件:

TaskExtensions.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");
    }
}
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
        }
    }
}
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;
    }
}
公共静态类任务扩展
{
带取消的公共静态异步任务(
此任务任务(取消令牌取消令牌)
{
var tcs=new TaskCompletionSource();
使用(cancellationToken.Register(s=>((TaskCompletionSource)s.TrySetResult(true),tcs))
if(task!=等待task.wheny(task,tcs.task))
抛出新操作CanceledException(cancellationToken);
返回等待任务;
}
}
我不会详细介绍它是如何工作的,因为上面的文章对它进行了很好的解释

现在,当您捕捉到CTRL+C信号时,令牌被通知取消,这将抛出一个
OperationCanceledException
,从而中断该循环。我们抓住它,把它扔到一边,然后离开

如果要继续使用
AppDomain.CurrentDomain.ProcessExit
,可以--您的选择。。只需将代码添加到
控制台中即可。在该事件中按CancelKeyPress


然后程序将优雅地退出。。。嗯,尽可能优雅。

这有帮助吗@安迪谢谢,在添加
Console.CancelKeyPress+=(s,e)=>{…}
之后,控制台将捕获ctrl+c。CancelKeyPress和kill信号将被
AppDomain.CurrentDomain.ProcessExit
捕获。但是AppDomain.CurrentDomain.ProcessExit并不等待,它只打印
ProcessExit,wait task.delay()后的输出未打印我有一个附带问题。为什么要使用
HttpListener
?如果您使用的是.NETCore,为什么不使用Kestrel并获得一个性能更好的完整web服务器呢?下面是一个如何设置的示例:我将提供一个如何让所有内容优雅地退出的示例。我有一些想法。@Andy使用HttpListener简单地打开一个端口并确认请求,通过这个简单的web服务器impl,我可以向Consor代理注册一个HTTP检查,谢谢你的建议,我会看看它,做kestrel impl简单的