Windows 服务内部轮询的常见做法

问题描述 投票:0回答:2

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

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 使用率。使用没有超时的轮询是一个糟糕的选择吗?
c# .net multithreading windows-services long-polling
2个回答
3
投票

有一种非常简单的方法可以做到这一点,前提是您并不严格需要有序关闭。 如果您将

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()
,并在服务指示其正在关闭时在战略点退出。

希望这有帮助。


0
投票
如何在 do work 周围放置一个序列化并设置一个变量,以便在调用 o stop 时它将检查变量并在必要时取消停止?

© www.soinside.com 2019 - 2024. All rights reserved.