C# 如何在linux上的asp.net内核中捕获退出信号?
我正在写一个基于NetCore3.1Linux的c#控制台应用程序 预计会有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
- 异步运行作业
- 等待工作结束
- 抓住杀戮信号,干净利落
命名空间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简单的