当前我们的应用原来越多的使用异步方法,而异步方法通常会有一个 CancellationToken
的参数以及时取消我们的异步操作,那我们如何获取一个应用退出的 CancellationToken
呢,我们只需要使用一个 CancellationTokenSource
在应用退出的时候将其 Cancel 即可
对于简单的 Ctrl+C 退出的我们可以通过 Console.CancelKeyPress
事件来处理,实现如下:
public static class ConsoleHelper
{
static ConsoleHelper()
{
Console.CancelKeyPress += (sender, args) =>
{
CancellationTokenSource.Cancel(false);
};
}
public static CancellationToken GetExitToken()
{
return CancellationTokenSource.Token;
}
private static readonly CancellationTokenSource CancellationTokenSource = new();
}
我们想要注册退出事件的话可以通过这个 CancellationToken
来 Register
我们来测试一下,测试代码如下:
var exitToken = ConsoleHelper.GetExitToken();
exitToken.Register(() => Console.WriteLine(@"Console exiting"));
Console.ReadLine();
我们稍微改造一下测试代码,在回调里增加三秒的等待
var exitToken = ConsoleHelper.GetExitToken();
exitToken.Register(() =>
{
Console.WriteLine(@"Console exiting");
Thread.Sleep(3000);
Console.WriteLine(@"Console exited");
});
Console.WriteLine("starting");
Console.ReadLine();
从输出结果可以看到我们的 Console exited
并没有被打印出来
如果注意的话会发现 CancelKeyPress
的事件参数 ConsoleCancelEventArgs
里有一个 Cancel
的属性,默认值是 false
我们如果设置为 true
则会取消结束进程,asp.net core 也是借助于此来实现 graceful shutdown
我们再来改造一下 exitToken 再事件处理的时候设置为 true
args.Cancel = true;
改造一下示例:
var exitToken = ConsoleHelper.GetExitToken();
exitToken.Register(() =>
{
Console.WriteLine(@"Console exiting");
Thread.Sleep(3000);
Console.WriteLine(@"Console exited");
});
Console.WriteLine("starting");
Console.ReadLine();
Console.WriteLine("exiting");
Console.ReadLine();
我们再将 Cancel
的设置去掉试一下
不知道你是否看出来其中区别,设置为 true
的时候 block 到最后的 Console.ReadLine()
了,不设置的时候进程终止了,可以根据自己需要进行调整
前面我们只处理了 Console.CancelKeyPress
的事件,实际进程有可能会被外部强制终止,这些情况基本不会被捕获,需要额外的设置才可以
我们可以在 dotnet core 的 Hosting 部分 ConsoleLifetime
的代码里找到注册应用退出事件的方法
前段时间也看到石头哥发的他们测试的各种应用退出的测试,这里借用一下石头哥的测试结果:
感兴趣的可以参考:https://newlifex.com/blood/elegant_exit,也找了一下他们应用退出的注册,大部分是一样的
有一个 SIGQUIT
在 ConsoleLifetime
里有注册,石头哥他们库里没注册,给他们提了一个 PR
https://github.com/NewLifeX/X/pull/128
用到的所有的注册方法如下:
static void InvokeExitHandler(object? sender, EventArgs? args);
// https://github.com/NewLifeX/X/blob/e65dfa0998ec393804f3f793f333c237110d890e/NewLife.Core/Model/Host.cs#L61
// https://github.com/dotnet/runtime/blob/940b332ad04e58862febe019788a5b21e266ea10/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.notnetcoreapp.cs
AppDomain.CurrentDomain.ProcessExit += InvokeExitHandler;
Console.CancelKeyPress += InvokeExitHandler;
#if NETCOREAPP
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx => InvokeExitHandler(ctx, );
#endif
#if NET6_0_OR_GREATER
// https://github.com/dotnet/runtime/blob/940b332ad04e58862febe019788a5b21e266ea10/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.netcoreapp.cs
PosixSignalRegistration.Create(PosixSignal.SIGINT, ctx => InvokeExitHandler(ctx, ));
PosixSignalRegistration.Create(PosixSignal.SIGQUIT, ctx => InvokeExitHandler(ctx, ));
PosixSignalRegistration.Create(PosixSignal.SIGTERM, ctx => InvokeExitHandler(ctx, ));
#endif
https://github.com/WeihanLi/WeihanLi.Common/blob/578c5ba80bad9b8073ae6dec3403f884a7ab4e84/src/WeihanLi.Common/Helpers/InvokeHelper.cs#L12
实现代码如下:
private static readonly object _exitLock = new();
private static volatile bool _exited;
private static readonly Lazy LazyCancellationTokenSource = new();
public static CancellationToken GetExitToken() => LazyCancellationTokenSource.Value.Token;
private static void InvokeExitHandler(object? sender, EventArgs? args)
{
if (_exited) return;
lock (_exitLock)
{
if (_exited) return;
Debug.WriteLine("exiting...");
if (LazyCancellationTokenSource.IsValueCreated)
{
LazyCancellationTokenSource.Value.Cancel(false);
LazyCancellationTokenSource.Value.Dispose();
}
Debug.WriteLine("exited");
_exited = true;
}
}
CancellationTokenSource
使用 Lazy
做懒初始化,用不到的时候就不创建,通过 _exited
和 _exitLock
避免 exit 方法多次执行,如果 CancellationTokenSource
没有创建的话,也就无需处理,如果创建了,就触发 CancellationToken
并在触发之后 Dispose
使用起来和前面 ConsoleHelper.GetExitToken()
类似
var exitToken = InvokeHelper.GetExitToken();
exitToken.Register(() =>
{
Console.WriteLine(@"Exiting");
Thread.Sleep(3000);
Console.WriteLine(@"Exited");
});
while(!exitToken.IsCancellationRequested)
{
System.Console.WriteLine(DateTimeOffset.Now);
await Task.Delay(1000);
}
输出结果如下:
页面更新:2024-05-06
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号