假设您有一个多线程Windows服务,它执行许多不同的操作,这需要花费相当多的时间,例如:从不同的数据存储中提取数据,解析所述数据,将其发布到外部服务器等。可以在不同的层中执行操作,例如,应用程序层,存储库层或服务层。
在此Windows服务的生命周期的某个时刻,您可能希望通过services.msc将其关闭或重新启动,但是如果您无法停止所有操作并终止Windows服务中的所有线程services.msc期望使用停止过程完成的时间跨度,它将挂起,您将不得不从任务管理器中删除它。
由于上述问题,我的问题如下:如何实现处理Windows服务关闭的故障安全方法?我有一个volatile布尔值作为关闭信号,由我的服务基类中的OnStop()启用,并且应该优雅地停止我的主循环,但是如果在其他某个层中有一个操作,那就没有任何价值。正在花时间做任何操作。
应如何处理?我目前处于亏损状态,需要一些创意投入。
答案 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。