优雅地处理Windows服务的关闭

Mar*_*tim 7 c# multithreading windows-services

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

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

由于上面提到的问题,我的问题如下:如何实现处理Windows服务关闭的故障安全方法?我有一个挥发性布尔值作为关闭信号,由我的服务基类中的OnStop()启用,并且应该优雅地停止我的主循环,但如果在某个其他层中有一个操作,这是不值得的是时候做任何操作了.

应如何处理?我目前处于亏损状态,需要一些创造性的投入.

Mar*_* N. 6

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

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

你也应该知道ServiceBase.RequestAdditionalTime.如果无法在适当的时候取消所有任务,您可以申请延长期.

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

线程是后台线程或前台线程.后台线程与前台线程相同,除了后台线程不会阻止进程终止.一旦属于进程的所有前台线程终止,公共语言运行库就结束该进程.任何剩余的后台线程都会停止并且不会完成.


Mar*_*tim 4

经过更多的研究和一些头脑风暴,我开始意识到我遇到的问题是由 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
}
Run Code Online (Sandbox Code Playgroud)

这是我提到的非常常见的设计缺陷。问题是,虽然这会按预期编译和运行,但您最终会遇到 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
Run Code Online (Sandbox Code Playgroud)

打回来:

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()
Run Code Online (Sandbox Code Playgroud)

停止:

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
Run Code Online (Sandbox Code Playgroud)

结论

通过实现 System.Threading.Timer 和 ManualResetEvent,您将避免服务由于 Thread.Sleep() 阻塞而对服务控制台命令无响应。

附言!您可能还没有脱离困境!

但是,我相信在某些情况下,程序员为回调分配了太多工作,导致服务在工作负载执行期间可能对服务控制台命令无响应。如果发生这种情况,您可能希望查看替代解决方案,例如在代码中更深入地检查 ManualResetEvent,或者可能实现 CancellationTokenSource。