繁体   English   中英

优雅地处理Windows服务的关闭

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM