Thread.Sleep的替代品

时间:2012-03-24 16:11:36

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

处理Windows服务,该服务必须在每个预定义的时间间隔内处理请求。 Thread.Sleep完美地完成了这项工作但问题是当调用服务停止时,如果线程处于睡眠模式则服务冻结。 我已经阅读过像Timer这样的替代方法,但问题是在定义的时间间隔之后新线程开始了。 是否有更好的方法来实现相同的结果,而不是陷入问题。

5 个答案:

答案 0 :(得分: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,您可以通过几种不同的方式执行此操作。您可以创建使用ThreadPool的后台线程,即Thread.Sleep。当线程唤醒时,设置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");
    }
}

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

答案 1 :(得分:2)

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

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

然后,当调用Service stop时,您可以触发事件

 seviceStopRequested .Set();

答案 2 :(得分:2)

我通常使用以下模式:

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秒会暂停,直到再次更改

因此,它应符合您的所有要求。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

对于什么是值得的,.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();
  }
}