【问题标题】:Alternative to Thread.Sleep替代 Thread.Sleep
【发布时间】:2012-04-08 20:10:09
【问题描述】:

在 Windows 服务上工作,该服务必须在每个预定义的时间间隔内处理请求。 Thread.Sleep 可以很好地完成工作,但问题是当调用停止服务时,如果线程处于睡眠模式,则服务冻结。 我已经阅读了诸如 Timer 之类的替代方法,但问题在于在定义的时间间隔之后,新线程开始启动。 有没有更好的方法来实现相同的结果并且不会遇到问题。

【问题讨论】:

  • 启动一个新线程并让该线程进入睡眠状态,而不是让服务进入睡眠状态。这似乎是一个设计问题。
  • 你为什么不买一个定时唤醒的定时器?
  • 为什么不使用事件来表示新请求已准备好并等待该事件。睡眠和计时器是二流的解决方案。

标签: c# multithreading .net-3.5 windows-services


【解决方案1】:

我通常使用以下模式:

public class MyJob
{
    System.Threading.Timer _timer;
    bool _isStopped;

    public void MyJob()
    {
        _timer = new Timer(OnWork, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(-1));
    }

    private void OnWork(object state)
    {
        //[.. do the actual work here ..]

        if (!_isStopped)
            _timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(-1));
    }

    public void Stop()
    {
        _isStopped = true;
        _timer.Change(TimeSpan.FromSeconds(-1), TimeSpan.FromSeconds(-1));
    }

    public void Start()
    {
        _isStopped = false;
        _timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(-1));
    }
}

关键点:

  • 只有使用初始间隔,您才能完全控制计时器何时再次启动(即工作时间不计入计时器间隔)
  • 将计时器更改为 -1 秒会暂停,直到再次更改

因此它应该可以满足您的所有要求。

【讨论】:

    【解决方案2】:

    .NET BCL 中的大多数阻塞调用都将响应Thread.Interrupt。也就是说,它们不会等待调用时指定的全部时间,而是立即返回。但是,我会避免使用这种方法,而是使用单个ManualResetEvent 来执行空闲等待和关闭信号。它看起来像这样。

    public class MyServer : ServiceBase
    {
      private ManualResetEvent shutdown = new ManualResetEvent(false);
    
      protected override void OnStart(string[] args)
      {
        new Thread(
          () =>
          {
            while (!shutdown.WaitOne(YourInterval))
            {
              // Do work here.
            }
          }).Start();
      }
    
      protected override void OnStop()
      {
        shutdown.Set();
      }
    }
    

    【讨论】:

      【解决方案3】:

      您正在寻找的是能够响应两个不同事件的通知 - (1) 计时器到期时和 (2) 服务停止时。 @Anurag RanhjanWaitHandle 走在正确的轨道上,但是您有两个事件,而不是一个。要正确处理此问题,请执行以下操作。

      首先,使用ManualResetEvent 定义您关心的两个事件。如果您愿意,可以使用AutoResetEvent;我只是更喜欢手动重置事件。

      using System.Threading;
      ManualResetEvent shutdownEvent = new ManualResetEvent();
      ManualResetEvent elapsedEvent = new ManualResetEvent();
      

      您需要在这些事件发生时触发它们。对于shutdownEvent,这很容易。在您的 Windows 服务的 OnStop 回调中,只需设置事件即可。

      protected override void OnStop
      {
          shutdownEvent.Set();
      }
      

      对于elapsedEvent,您可以通过几种不同的方式执行此操作。您可以创建一个使用Thread.Sleep 的后台线程,即ThreadPool。当线程唤醒时,设置elapsedEvent 并重新进入睡眠状态。由于它是后台线程,因此它在关闭时不会挂起您的服务。正如您已经建议的那样,替代方法是使用计时器。我就是这样做的。

      using System.Timers;
      Timer timer = new Timer();
      timer.Interval  = 5000;   // in milliseconds
      timer.Elapsed  += delegate { elapsedEvent.Set(); };
      timer.AutoReset = false;  // again, I prefer manual control
      timer.Start();
      

      既然您已经正确设置了事件,请将它们放入 WaitHandle 数组中。

      WaitHandle[] handles = new WaitHandle[]
      {
          shutdownEvent,
          elapsedEvent
      };
      

      代替WaitHandle.WaitOne 方法,在while 循环中使用WaitHandle.WaitAny 方法,如下所示。

      while (!shutdownEvent.WaitOne())
      {
          switch (WaitHandle.WaitAny(handles))
          {
              case 0:  // The shutdownEvent was triggered!
                  break;
              case 1:  // The elapsedEvent was triggered!
                  Process();             // do your processing here
                  elapsedEvent.Reset();  // reset the event manually
                  timer.Start();         // restart the timer manually
                  break;
              default:
                  throw new Exception("unexpected switch case");
          }
      }
      

      我从我的项目中的生产代码中浓缩了这个示例。我知道这种机制有效,但我可能在文章中遗漏了一些东西。如果您有任何问题,请告诉我。

      【讨论】:

      • 请不要推荐System.Timers.Timer。它吃掉未捕获的异常,这使得一切似乎都正常工作,而它可能不起作用。改为使用System.Threading.Timer 的示例。
      【解决方案4】:

      使用计时器将命令/任务(包括关闭任务)添加到阻塞队列。让您的服务线程等待阻塞队列上的任务并在可用时执行它们。定时器线程会定期将任务添加到队列中。

      【讨论】:

        【解决方案5】:

        您可以改用WaitHandle.WaitOne。您可以等待在预定义的时间间隔中指定的关闭事件触发或超时。

         static AutoResetEvent seviceStopRequested = new AutoResetEvent(false);
         ....
         ((AutoResetEvent)stateInfo).WaitOne([timeout], false)
        

        那么当Service stop被调用时,就可以触发事件了

         seviceStopRequested .Set();
        

        【讨论】:

          猜你喜欢
          • 2023-03-12
          • 1970-01-01
          • 2016-01-13
          • 2012-07-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-16
          相关资源
          最近更新 更多