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

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

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

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 使用率。使用没有超时的轮询是不是一个糟糕的选择?

最佳答案

有一种非常简单的方法可以做到这一点,前提是您不需要严格按顺序关机。如果将 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 中打开的状态。关键是,这种方法可以使用,但仅限于特定情况。

更好的方法是完全过渡到基于事件的机制。它比轮询更有效,并且允许有序地关闭您的服务。下面是一个带有注释的示例。

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

希望这对您有所帮助。

关于c# - Windows 服务内部轮询的常见做法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33015657/

相关文章:

c# - unity计数标签序列化

c# - 使用默认凭据在控制台应用程序中调用 api

.net - 未找到或无法加载程序集 mscorlib.dll

c++ - 以下单例实现线程安全吗?

c# - LINQ to SQL - 在测试和开发数据库之间切换的最佳方式

c# - AutoPostback 没有正确设置焦点

具有公共(public) 'Instance' 字段而不是属性的 C# 单例

c# - ServiceProcess.ServiceController 是如何工作的?

python - 显示我的多线程进程的进度条

vb.net - 进度条和代码位置的多线程(vb.net)?