【问题标题】:The common practice for polling inside Windows servicesWindows 服务内部轮询的常见做法
【发布时间】:2015-10-08 12:21:02
【问题描述】:

通常建议您使用类似的东西(使用超时):

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; 
    }
}
  1. 这些结构本质上是否相等:

    while(!this.finishedEvent.WaitOne(0, false))

    while(true) 没有finishedEvent

  2. 我听说超时用于减少 CPU 使用率。在没有超时的情况下使用轮询是一个糟糕的选择吗?

【问题讨论】:

  • 不,它们不等价。您将永远无法使用 while(true) 循环停止服务。你会烧掉 100% 的核心,几乎一事无成。民意调查很糟糕,但有时您别无选择,因为您无法让事件告诉您发生了有趣的事情。视情况而定。
  • 好的,我明白了!因此,当您在单独的(工作)线程上时使用 AutoResetEvent 是向服务发出 stop / start 信号的方式。

标签: c# .net multithreading windows-services long-polling


【解决方案1】:

有一种非常简单的方法可以做到这一点,前提是您不需要有序关机。如果您将workerThread 标记为background thread,它将在服务停止时自动关闭。在此示例中,您可以放弃使用 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 中打开的状态。关键是,这种方法可以使用,但仅限于特定情况。

更好的方法是完全过渡到基于事件的机制。它比轮询效率高得多,并且可以有序地关闭您的服务。下面是一个使用 cmets 的示例。

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 时完成的工作不会花费太长时间。换句话说,while 循环不会检查_shutdownEvent,直到DoProcessing() 方法退出。因此,您需要限制在 DoProcessing() 方法中花费的时间。如果该方法长时间运行,那么您可能需要检查 DoProcessing() 内的 _shutdownEvent 并在服务指示它正在关闭时在战略点退出。

希望这会有所帮助。

【讨论】:

  • 很好的解释!谢谢您的回答。上帝保佑你奉耶稣的名!
  • 你也是,兄弟。 ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-21
  • 2017-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-19
  • 2021-05-22
相关资源
最近更新 更多