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