[英]Gracefully handling shutdown of a Windows service
假设您有一个多线程Windows服务,它执行大量不同的操作,这需要花费相当多的时间,例如从不同的数据存储中提取数据,解析所述数据,将其发布到外部服务器等。操作可以在不同的时间执行层,例如应用层,存储库层或服务层。
在此Windows服务的生命周期中的某个时刻,您可能希望通过services.msc关闭它或重新启动它,但是如果您无法在服务的时间跨度内停止所有操作并终止Windows服务中的所有线程。 msc期望用stop程序完成,它会挂起,你必须从任务管理器中删除它。
由于上面提到的问题,我的问题如下:如何实现处理Windows服务关闭的故障安全方法? 我有一个挥发性布尔值作为关闭信号,由我的服务基类中的OnStop()启用,并且应该优雅地停止我的主循环,但如果在某个其他层中有一个操作,这是不值得的是时候做任何操作了。
应如何处理? 我目前处于亏损状态,需要一些创造性的投入。
我将使用CancellationTokenSource
并将取消令牌从OnStop
方法传播到所有层,并在那里启动所有线程和任务。 它在框架中,所以如果你关心它,它不会破坏你的松散耦合(我的意思是,无论你在哪里使用线程/任务,你也有'CancellationToken'可用。
这意味着您需要调整异步方法以考虑取消令牌。
您还应该了解ServiceBase.RequestAdditionalTime
。 如果无法在适当的时候取消所有任务,您可以申请延长期。
或者,也许您可以探索IsBackground
替代方案。 当进程即将退出时,CLR会停止Windows服务中启用了此功能的所有线程:
线程是后台线程或前台线程。 后台线程与前台线程相同,除了后台线程不会阻止进程终止。 一旦属于进程的所有前台线程终止,公共语言运行库就结束该进程。 任何剩余的后台线程都会停止并且不会完成。
经过更多的研究和一些头脑风暴,我逐渐意识到我遇到的问题是由于Windows服务中线程的一个非常常见的设计缺陷引起的。
设计缺陷
想象一下,你有一个完成所有工作的线程。 您的工作包括应该无限期地一次又一次地运行的任务。 这通常实现如下:
volatile bool keepRunning = true;
Thread workerThread;
protected override void OnStart(string[] args)
{
workerThread = new Thread(() =>
{
while(keepRunning)
{
DoWork();
Thread.Sleep(10 * 60 * 1000); // Sleep for ten minutes
}
});
workerThread.Start();
}
protected override void OnStop()
{
keepRunning = false;
workerThread.Join();
// Ended gracefully
}
这是我提到的非常常见的设计缺陷。 问题是,虽然这将按预期编译和运行,但您最终将体验到Windows服务不会响应来自Windows服务控制台的命令。 这是因为您对Thread.Sleep()的调用阻止了该线程,导致您的服务无响应。 如果线程阻塞的时间超过Windows在HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ WaitToKillServiceTimeout中配置的超时,则只会遇到此错误,因为此注册表值如果您的线程配置为非常睡眠,则此实现可能适用于您短时间内,它是否在可接受的时间段内工作。
替代方案
而不是使用Thread.Sleep()我决定改为使用ManualResetEvent和System.Threading.Timer。 实现看起来像这样:
的OnStart:
this._workerTimer = new Timer(new TimerCallback(this._worker.DoWork));
this._workerTimer.Change(0, Timeout.Infinite); // This tells the timer to perform the callback right now
打回来:
if (MyServiceBase.ShutdownEvent.WaitOne(0)) // My static ManualResetEvent
return; // Exit callback
// Perform lots of work here
ThisMethodDoesAnEnormousAmountOfWork();
(stateInfo as Timer).Change(_waitForSeconds * 1000, Timeout.Infinite); // This tells the timer to execute the callback after a specified period of time. This is the amount of time that was previously passed to Thread.Sleep()
调用OnStop:
MyServiceBase.ShutdownEvent.Set(); // This signals the callback to never ever perform any work again
this._workerTimer.Dispose(); // Dispose of the timer so that the callback is never ever called again
结论
通过实现System.Threading.Timer和ManualResetEvent,您将避免由于Thread.Sleep()阻塞而导致服务无法响应服务控制台命令。
PS! 你可能还没有走出困境!
但是,我认为在某些情况下,程序员会为回调分配这么多工作,使得服务可能在工作负载执行期间对服务控制台命令没有响应。 如果发生这种情况,您可能希望查看其他解决方案,例如在代码中更深入地检查ManualResetEvent,或者实现CancellationTokenSource。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.