简体   繁体   English

优雅地处理Windows服务的关闭

[英]Gracefully handling shutdown of a Windows service

Assume that you have a multi-threaded Windows service which performs lots of different operations which takes a fair share of time, eg extracting data from different data stores, parsing said data, posting it to an external server etc. Operations may be performed in different layers, eg application layer, repository layer or service layer. 假设您有一个多线程Windows服务,它执行大量不同的操作,这需要花费相当多的时间,例如从不同的数据存储中提取数据,解析所述数据,将其发布到外部服务器等。操作可以在不同的时间执行层,例如应用层,存储库层或服务层。

At some point in the lifespan of this Windows service you may wish to shut it down or restart it by way of services.msc, however if you can't stop all operations and terminate all threads in the Windows service within the timespan that services.msc expects to be done with the stop procedure, it will hang and you will have to kill it from Task Manager. 在此Windows服务的生命周期中的某个时刻,您可能希望通过services.msc关闭它或重新启动它,但是如果您无法在服务的时间跨度内停止所有操作并终止Windows服务中的所有线程。 msc期望用stop程序完成,它会挂起,你必须从任务管理器中删除它。

Because of the issue mentioned above, my question is as follows: How would you implement a fail-safe way of handling shutdown of your Windows service? 由于上面提到的问题,我的问题如下:如何实现处理Windows服务关闭的故障安全方法? I have a volatile boolean that acts as a shutdown signal, enabled by OnStop() in my service base class, and should gracefully stop my main loop, but that isn't worth anything if there is an operation in some other layer which is taking it's time doing whatever that operation is up to. 我有一个挥发性布尔值作为关闭信号,由我的服务基类中的OnStop()启用,并且应该优雅地停止我的主循环,但如果在某个其他层中有一个操作,这是不值得的是时候做任何操作了。

How should this be handled? 应如何处理? I'm currently at a loss and need some creative input. 我目前处于亏损状态,需要一些创造性的投入。

I would use a CancellationTokenSource and propagate the cancellation token from the OnStop method down to all layers and all threads and tasks started there. 我将使用CancellationTokenSource并将取消令牌从OnStop方法传播到所有层,并在那里启动所有线程和任务。 It's in the framework, so it will not break your loose coupling if you care about that (I mean, wherever you use a thread/Task you also have `CancellationToken' available. 它在框架中,所以如果你关心它,它不会破坏你的松散耦合(我的意思是,无论你在哪里使用线程/任务,你也有'CancellationToken'可用。

This means you need to adjust your async methods to take the cancellation token into consideration. 这意味着您需要调整异步方法以考虑取消令牌。

You should also be aware of ServiceBase.RequestAdditionalTime . 您还应该了解ServiceBase.RequestAdditionalTime In case it is not possible to cancel all tasks in due time, you can request an extension period. 如果无法在适当的时候取消所有任务,您可以申请延长期。

Alternatively, maybe you can explore the IsBackground alternative. 或者,也许您可​​以探索IsBackground替代方案。 All threads in your windows service that have this enabled are stopped by the CLR when the process is about to exit: 当进程即将退出时,CLR会停止Windows服务中启用了此功能的所有线程:

A thread is either a background thread or a foreground thread. 线程是后台线程或前台线程。 Background threads are identical to foreground threads, except that background threads do not prevent a process from terminating. 后台线程与前台线程相同,除了后台线程不会阻止进程终止。 Once all foreground threads belonging to a process have terminated, the common language runtime ends the process. 一旦属于进程的所有前台线程终止,公共语言运行库就结束该进程。 Any remaining background threads are stopped and do not complete. 任何剩余的后台线程都会停止并且不会完成。

After more research and some brainstorming I came to realise that the problems I've been experiencing were being caused by a very common design flaw regarding threads in Windows services. 经过更多的研究和一些头脑风暴,我逐渐意识到我遇到的问题是由于Windows服务中线程的一个非常常见的设计缺陷引起的。

The design flaw 设计缺陷

Imagine you have a thread which does all your work. 想象一下,你有一个完成所有工作的线程。 Your work consists of tasks that should be run again and again indefinitely. 您的工作包括应该无限期地一次又一次地运行的任务。 This is quite often implemented as follows: 这通常实现如下:

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
}

This is the very common design flaw I mentioned. 这是我提到的非常常见的设计缺陷。 The problem is that while this will compile and run as expected, you will eventually experience that your Windows service won't respond to commands from the service console in Windows. 问题是,虽然这将按预期编译和运行,但您最终将体验到Windows服务不会响应来自Windows服务控制台的命令。 This is because your call to Thread.Sleep() blocks the thread, causing your service to become unresponsive. 这是因为您对Thread.Sleep()的调用阻止了该线程,导致您的服务无响应。 You will only experience this error if the thread blocks for longer than the timeout configured by Windows in HKLM\\SYSTEM\\CurrentControlSet\\Control\\WaitToKillServiceTimeout, because of this registry value this implementation may work for you if your thread is configured to sleep for a very short period of time and does it's work in an acceptable period of time. 如果线程阻塞的时间超过Windows在HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ WaitToKillServiceTimeout中配置的超时,则只会遇到此错误,因为此注册表值如果您的线程配置为非常睡眠,则此实现可能适用于您短时间内,它是否在可接受的时间段内工作。

The alternative 替代方案

Instead of using Thread.Sleep() I decided to go for ManualResetEvent and System.Threading.Timer instead. 而不是使用Thread.Sleep()我决定改为使用ManualResetEvent和System.Threading.Timer。 The implementation looks something like this: 实现看起来像这样:

OnStart: 的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

Callback: 打回来:

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: 调用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

The conclusion 结论

By implementing System.Threading.Timer and ManualResetEvent you will avoid your service becoming unresponsive to service console commands as a result of Thread.Sleep() blocking. 通过实现System.Threading.Timer和ManualResetEvent,您将避免由于Thread.Sleep()阻塞而导致服务无法响应服务控制台命令。

PS! PS! You may not be out of the woods just yet! 你可能还没有走出困境!

However, I believe there are cases in which a callback is assigned so much work by the programmer that the service may become unresponsive to service console commands during workload execution. 但是,我认为在某些情况下,程序员会为回调分配这么多工作,使得服务可能在工作负载执行期间对服务控制台命令没有响应。 If that happens you may wish to look at alternative solutions, like checking your ManualResetEvent deeper in your code, or perhaps implementing CancellationTokenSource. 如果发生这种情况,您可能希望查看其他解决方案,例如在代码中更深入地检查ManualResetEvent,或者实现CancellationTokenSource。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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