优雅地处理Windows服务的关闭

时间:2014-08-07 09:12:35

标签: c# multithreading windows-services

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

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

由于上述问题,我的问题如下:如何实现处理Windows服务关闭的故障安全方法?我有一个volatile布尔值作为关闭信号,由我的服务基类中的OnStop()启用,并且应该优雅地停止我的主循环,但是如果在其他某个层中有一个操作,那就没有任何价值。正在花时间做任何操作。

应如何处理?我目前处于亏损状态,需要一些创意投入。

2 个答案:

答案 0 :(得分:6)

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

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

您还应该了解ServiceBase.RequestAdditionalTime。如果无法在适当的时候取消所有任务,您可以申请延长期。

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

  

线程是后台线程或前台线程。   后台线程与前台线程相同,除此之外   后台线程不会阻止进程终止。一切都好   属于某个进程的前台线程已经终止,这是常见的   语言运行时结束进程。任何剩余的后台线程   停止了,没有完成。

答案 1 :(得分:3)

经过更多研究和一些头脑风暴后,我逐渐意识到我遇到的问题是由于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。