Альтернатива Thread.Sleep - PullRequest
3 голосов
/ 24 марта 2012

Работа над службой Windows, которая должна обрабатывать запрос в каждый заданный интервал времени. Thread.Sleep прекрасно работает, но проблема в том, что когда служба вызывается для остановки, служба останавливается, если поток находится в спящем режиме. Я читал об альтернативном подходе, таком как Timer, но проблема в том, что после этого определенного интервала начинается новый поток. Есть ли лучший способ добиться того же результата и не прибегать к выдаче.

Ответы [ 5 ]

3 голосов
/ 24 марта 2012

Что вам нужно, так это возможность отвечать на уведомления о двух разных событиях - (1) по истечении таймера и (2) при остановке службы. @ Anurag Ranhjan находится на правильном пути с WaitHandle, но у вас есть два события, а не одно. Чтобы правильно справиться с этим, сделайте следующее.

Сначала определите два события, которые вас интересуют, используя ManualResetEvent. Вы можете использовать AutoResetEvent, если хотите; Я просто предпочитаю сброс событий вручную.

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

Вам нужно запускать эти события, когда они происходят. Для shutdownEvent это легко. В обратном вызове OnStop вашей службы Windows просто установите событие.

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 используйте метод WaitHandle.WaitAny внутри цикла while, как этот.

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");
    }
}

Я сжал этот пример из производственного кода в моем проекте. Я знаю, что этот механизм работает, но я мог что-то пропустить в рецензии. Дайте мне знать, если у вас есть какие-либо вопросы.

2 голосов
/ 26 марта 2012

Я обычно использую следующий шаблон:

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 голосов
/ 24 марта 2012

Вместо этого вы можете использовать WaitHandle.WaitOne .Вы можете подождать, пока не завершится событие закрытия или тайм-аут, указанный в предварительно определенный интервал времени .

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

Затем, когда вызывается останов службы, вы можете просто вызвать событие

 seviceStopRequested .Set();
1 голос
/ 24 марта 2012

Используйте таймер для добавления команд / задач, включая задачу для отключения, в очередь блокировки. Сделайте так, чтобы ваш служебный поток ожидал выполнения задач в очереди блокировки и выполнял их, когда они были доступны. Поток таймера будет периодически добавлять задачи в очередь.

0 голосов
/ 26 марта 2012

За то, что стоит, большинство блокирующих вызовов в .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();
  }
}
...