【问题标题】:Gracefully handling shutdown of a Windows service优雅地处理 Windows 服务的关闭
【发布时间】:2014-08-07 09:12:35
【问题描述】:

假设您有一个多线程 Windows 服务,它执行许多不同的操作,这些操作需要相当多的时间,例如从不同的数据存储中提取数据,解析所述数据,将其发布到外部服务器等。操作可以在不同的层中执行,例如应用层、存储层或服务层。

在此 Windows 服务生命周期的某个时间点,您可能希望通过 services.msc 将其关闭或重新启动,但是如果您无法在时间跨度内停止所有操作并终止 Windows 服务中的所有线程services.msc 期望通过停止过程完成,它将挂起,您必须从任务管理器中将其杀死。

由于上述问题,我的问题如下:您将如何实现一种故障安全方式来处理 Windows 服务的关闭?我有一个 volatile 布尔值,它充当关闭信号,由我的服务基类中的 OnStop() 启用,并且应该优雅地停止我的主循环,但如果在其他一些层中有一个操作正在占用,那将毫无价值是时候执行该操作了。

应该如何处理?我目前不知所措,需要一些创造性的意见。

【问题讨论】:

    标签: c# multithreading windows-services


    【解决方案1】:

    我将使用CancellationTokenSource 并将取消令牌从OnStop 方法传播到所有层以及从那里开始的所有线程和任务。它在框架中,所以如果你关心它,它不会破坏你的松散耦合(我的意思是,无论你在哪里使用线程/任务,你也有 `CancellationToken' 可用。

    这意味着您需要调整异步方法以考虑取消令牌。

    您还应该知道ServiceBase.RequestAdditionalTime。如果无法按时取消所有任务,您可以申请延长期限。

    或者,也许您可​​以探索IsBackground 替代方案。当进程即将退出时,您的 Windows 服务中启用此功能的所有线程都会被 CLR 停止:

    线程要么是后台线程,要么是前台线程。 后台线程与前台线程相同,除了 后台线程不会阻止进程终止。一旦所有 属于一个进程的前台线程已经终止,常见的 语言运行时结束该过程。任何剩余的后台线程 已停止且未完成。

    【讨论】:

    • 我不觉得像用户存储库这样关心与线程相关的任何事情都没有关系,而且如果我要添加这段代码,这听起来可能会严重混淆我的存储库与我的存储库类中的线程和取消有关。是我误解了,还是你的意思是让我继续?
    • @Maritim:我没有任何意思,因为我不知道您的项目是如何设计的。但是,如果用户存储库启动一个任务/线程,那么它确实知道线程并且它可以使用取消令牌。你是直接用Thread还是Task/async/await。
    • @Maritim:那我明白你为什么不愿意修改你的代码了。但是,没有更简单的方法。 如果你认为在服务停止时可以取消所有线程,那么使用我上面提到的属性将它们设为后台线程。
    • 最坏的情况是部分数据无法完全传输,这不是危机,因为再次启动Windows服务时会传输数据。当服务停止时取消所有线程很可能是安全的。我将研究 IsBackground 替代方案。
    • 但如果有任何线程在工作,似乎不会调用 OnStop。所以我们不能依靠 OnStop 来停止线程......看这个:stackoverflow.com/questions/10643217/… - c-sharpcorner.com/forums/thread-pool-with-windows-service
    【解决方案2】:

    经过更多研究和一些头脑风暴后,我开始意识到我遇到的问题是由 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 中配置的超时时间时,您才会遇到此错误,由于此注册表值,如果您的线程配置为休眠很长时间,此实现可能对您有用短时间内,它是否在可接受的时间内发挥作用。

    替代方案

    我决定使用 ManualResetEvent 和 System.Threading.Timer 而不是使用 Thread.Sleep()。实现看起来像这样:

    开始:

    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。

    【讨论】:

    • 很高兴您找到了解决方案。如果在某个时候您决定转移到 Tasks 并使用 async/await 模式,请记住您可以将 Thread.Sleep 替换为 Task.Delay,它确实需要一个 CancellationToken。
    • 我不认为这是一个设计缺陷。您在休眠的线程上调用了 Join(),因此 OnStop() 的调用者被阻塞,直到线程唤醒。
    • @Maritim 我认为 WaitToKillServiceTimeout 是为了让服务仅在系统关闭时终止,而不是在服务控制台发出停止请求时终止。根据此来源:technet.microsoft.com/en-us/library/cc976045.aspx
    猜你喜欢
    • 2011-09-03
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    • 2019-05-23
    • 2015-05-15
    • 1970-01-01
    • 2011-04-30
    • 2010-09-06
    相关资源
    最近更新 更多