通常建议您使用类似的东西(使用超时):
Thread workerThread = null;
AutoResetEvent finishedEvent = new AutoResetEvent(false);
protected override void OnStart(string[] args) {
this.finishedEvent.Reset();
this.workerThread = new Thread(this.Poll);
this.workerThread.Start();
}
protected override void OnStop() {
this.finishedEvent.Set();
if(!this.workerThread.Join(2000)) {
this.RequestAdditionalTime(5000);
}
}
其中
Poll
函数定义如下:
private void Poll() {
try {
var timeout = Int32.Parse(ConfigurationManager.AppSettings["pollingTimeout"]);
while(!this.finishedEvent.WaitOne(timeout, false)) {
// do polling
}
}
catch(Exception ex) {
Logger.Log.Fatal(ex);
throw;
}
}
这些结构本质上是相等的吗:
while(!this.finishedEvent.WaitOne(0, false))
和
while(true)
没有 finishedEvent
?有一种非常简单的方法可以做到这一点,前提是您并不严格需要有序关闭。 如果您将
workerThread
标记为后台线程,当服务停止时它将自动关闭。 在此示例中,您可以放弃使用 finishedEvent
并使用无限循环。 例如,
Thread workerThread = null;
protected override void OnStart(string[] args)
{
this.workerThread = new Thread(this.DoWork);
// Signal the thread to stop automatically when the process exits.
this.workerThread.IsBackground = true;
this.workerThread.Start();
}
protected override void OnStop()
{
}
private void DoWork()
{
try
{
while (true)
{
// do your work here...
}
}
catch (Exception ex)
{
// handle exception here...
}
}
请注意,只有当您正在做的工作可以随时中断且不会产生不利影响时,才应使用此方法。 举例来说,您正在将数据写入 Excel 电子表格。 一旦 Windows 服务退出,您的
DoWork()
方法所代表的线程也会立即退出。 如果正在向电子表格添加数据,很可能电子表格的信息不完整,更糟糕的是,甚至可能处于无法在 Excel 中打开的状态。 关键是,这种方法可以使用,但仅限于某些情况。
更好的方法是完全过渡到基于事件的机制。 它比轮询更有效,并且允许有序关闭您的服务。 以下是带有注释的示例。
Thread _workThread = null;
// I use ManualResetEvent instead of AutoResetEvent because I do NOT want
// this event to EVER reset. It is meant to be set exactly one time.
ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
protected override void OnStart(string[] args)
{
_workThread = new Thread(DoWork());
_workThread.Start();
}
protected override void OnStop()
{
// Trigger the DoWork() method, i.e., the _workThread, to exit.
_shutdownEvent.Set();
// I always shutdown my service by simply joining the work thread.
// There are probably more advanced techniques that take into account
// longer shutdown cycles, but I design my worker thread(s) to have
// tight work cycles so that the shutdownEvent is examined frequently
// enough to facilitate timely shutdowns.
_workThread.Join();
}
现在让我们看看
DoWork()
方法的细节。 对于此示例,我将使用计时器来说明基于事件的方法。 请注意,此图与调用超时的 WaitOne()
方法没有本质区别。 但是,如果要完成的工作涉及处理来自其他线程的输入,例如从网络套接字接收数据的线程或从数据库读取数据的线程,则此方法可以轻松适应这些场景。
// Creature of habit. AutoResetEvent would probably work for this event,
// but I prefer to manually control when the event resets.
ManualResetEvent _timerElapsedEvent = new ManualResetEvent(false);
System.Timers.Timer _timer = null;
private void DoWork() {
try {
// Create, configure, and start the timer to elapse every second and
// require a manual restart (again, I prefer the manual control).
// Note when the timer elapses, it sets the _timerElapsedEvent.
_timer = new Timer(1000) { AutoReset = false };
_timer.Elapsed =+ (sender, e) => _timerElapsedEvent.Set();
_timer.Start();
// Create a WaitHandle array that contains the _shutdownEvent and
// the _timerElapsedEvent...in that order!
WaitHandle[] handles = new WaitHandle[] { _shutdownEvent, _timerElapsedEvent };
// Employ the event-based mechanism.
while (!_shutdownEvent.WaitOne(0)) {
switch (WaitHandle.WaitAny(handles) {
case 0:
// This case handles when the _shutdownEvent occurs,
// which will cause the while loop to exit.
break;
case 1:
// This case handles when the _timerElapsedEvent occurs.
// Do the work, reset the event, and restart the timer.
DoProcessing();
_timerElapsedEvent.Reset();
_timer.Start();
break;
}
}
} catch (Exception ex) {
// handle exception here...
}
}
WaitHandle
数组使基于事件的机制成为可能。 创建数组时,始终确保按优先级顺序将事件添加到数组中。 这就是为什么_shutdownEvent
列在_timerElapsedEvent
之前。 如果事件在数组中颠倒,则
_shutdownEvent
可能永远不会被处理。 您可以根据需要向
WaitHandle
数组添加任意数量的事件。 这就是这种方法如此灵活的原因。最后的想法。 为了便于及时关闭服务,您需要确保触发
_timerElapsedEvent
时完成的工作不会花费太长时间。 换句话说,在
_shutdownEvent
方法退出之前,
while
循环不会检查
DoProcessing()
。 因此,您需要限制在
DoProcessing()
方法中花费的时间。 如果该方法长时间运行,那么您可能需要检查
_shutdownEvent
内的
DoProcessing()
,并在服务指示其正在关闭时在战略点退出。希望这有帮助。