Служба Windows с AutoResetEvent - PullRequest
       24

Служба Windows с AutoResetEvent

4 голосов
/ 17 ноября 2010

В настоящее время я создаю службу Windows, которая должна обрабатывать очередь сообщений, которые содержатся в таблице базы данных.Длина этой очереди может варьироваться и может занять от 5 до 55 секунд для выполнения всех строк в базе данных (в настоящее время я использую тестовый набор данных из 500 000 записей)

Служба Windows настроена назапустить на 30-секундном таймере, поэтому я безуспешно пытался убедиться, что при запуске делегата таймера он не сможет работать снова, пока предыдущий запрос к методу не завершится успешно

У меня есть следующий кодв моем методе OnStart службы Windows:

     AutoResetEvent autoEvent = new AutoResetEvent(false);
     TimerCallback timerDelegate = new TimerCallback(MessageQueue.ProcessQueue);

     Timer stateTimer = new Timer(timerDelegate, autoEvent, 1000, Settings.Default.TimerInterval); // TimerInterval is 30000

     autoEvent.WaitOne();

И следующий код в MessageQueue.ProcessMessage:

      Trace.Write("Starting ProcessQueue");
      SmtpClient smtp = new SmtpClient("winprev-01");

      AutoResetEvent autoEvent = (AutoResetEvent)stateObject;

      foreach (MessageQueue message in AllUnprocessed)
      {
          switch (message.MessageType)
          {
              case MessageType.PlainText:
              case MessageType.HTML:
                  SendEmail(smtp, message);

                  break;

              case MessageType.SMS:
                  SendSms(message);

                  break;

              default:
                  break;
          }
      }

      autoEvent.Set();
      Trace.Write("Ending ProcessQueue");

Я использую DebugView для анализа представления операторов Trace при запуске службыи я вижу несколько экземпляров «Запуск ProcessQueue», которые происходят каждые 30 секунд, и это то, чего я пытаюсь избежать

В заключение: я хочу вызвать ProcessQueue и убедиться, что он не выполняется снова, если он не имеетзавершил свою работу (это позволяет мне предотвратить те же сообщения вочередь обрабатывается несколько раз

Я уверен, что мне здесь не хватает чего-то довольно очевидного, поэтому любая помощь будет принята с благодарностью:)

Дейв

Ответы [ 6 ]

2 голосов
/ 17 ноября 2010

Почему бы вашему делегату не отключить таймер, а затем повторно включить его (или продолжить работу, если время таймера истечет немедленно), как только он заработает. При условии, что задержка между срабатыванием таймера и пробуждением вашего делегата составляет <30 секунд, это должно быть водонепроницаемым. </p>

while (true)
{
  Trace.Write("Starting ProcessQueue")
  stateTimer.Enabled = false;
  DateTime start = DateTime.Now;

  // do the work

  // check if timer should be restarted, and for how long
  TimeSpan workTime = DateTime.Now - start;
  double seconds = workTime.TotalSeconds;
  if (seconds > 30)
  {
    // do the work again
    continue;
  }
  else
  {
     // Restart timer to pop at the appropriate time from now
     stateTimer.Interval = 30 - seconds;
     stateTimer.Enabled = true;
     break;
  }
}
2 голосов
/ 17 ноября 2010

Ваш ProcessMessage никогда не проверяет, сигнализируется ли resetEvent - он просто работает независимо от

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

У вас есть звонок на autoEvent.WaitOne() в неправильном месте; это должно быть в начале метода ProcessMessage.

AutoResetEvent autoEvent = (AutoResetEvent)stateObject;
autoEvent.WaitOne();
Trace.Write("Starting ProcessQueue");
SmtpClient smtp = new SmtpClient("winprev-01");
foreach (MessageQueue message in AllUnprocessed){

Вам также следует использовать перегрузку, которая принимает значение времени ожидания (int или timespan) и возвращает bool Если метод возвращает true, это означает, что ему было сообщено, поэтому вы можете продолжить. Если время истекло (потому что еще одна итерация все еще выполняется), вы должны просто вернуться и не пытаться запустить код снова.

Если вы не используете такую ​​перегрузку, то, что вы делаете, ничем не отличается от упаковки кода метода ProcessMessage в критическую секцию (например, lock() в глобальном var) - дополнительные потоки будут блокироваться, а затем без необходимости бежать.

AutoResetEvent autoEvent = (AutoResetEvent)stateObject;
//wait just one ms to see if it gets signaled; returns false if not
if(autoEvent.WaitOne(1)){  
    Trace.Write("Starting ProcessQueue");
    SmtpClient smtp = new SmtpClient("winprev-01");
    foreach (MessageQueue message in AllUnprocessed){

Обратите внимание, что на самом деле *ResetEvent здесь не идеален. Вы действительно просто хотите проверить, запущен ли экземпляр, и прервать работу, если это так. ResetEvent на самом деле не созданы для этого ... но я все равно хотел ответить на вопрос об использовании ResetEvent.

Что, вероятно, будет работать лучше, это просто выключить таймер при вызове обратного вызова, а затем перезапустить его, когда вы закончите. Таким образом, невозможно повторно ввести этот код, пока он еще работает.

Вы, безусловно, должны будете обернуть весь код в методе обратного вызова в try / finally, чтобы вы всегда перезапускали таймер после.

1 голос
/ 17 ноября 2010

Вы можете легко решить эту проблему, используя System.Threading.Timer.Вы делаете его однократным таймером, устанавливая его period на ноль.Перезапустите таймер в обратном вызове.Наложенное выполнение обратного вызова теперь невозможно.

Поскольку вы выполняете это так часто, другой подход заключается в использовании потока.Вам понадобится AutoResetEvent, чтобы сигнализировать об остановке потока в методе OnStop ().Его метод WaitOne () дает вам бесплатный таймер, когда вы используете перегрузку, которая принимает аргумент millisecondsTimeout .

Кстати: обратите внимание, что вызов autoEvent.WaitOne () в OnStart () проблематичен,Он может отключить контроллер сервиса, если отправка первого письма занимает много времени.Просто опустите его, у вас запущен таймер == служба запущена.

0 голосов
/ 17 ноября 2010

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

public class YourService : ServiceBase
{
  private ManualResetEvent m_Stop = new ManualResetEvent(false);

  protected override void OnStart(string[] args)
  {
    new Thread(Run).Start();
  }

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

  private void Run()
  {
    while (!m_Stop.WaitOne(TimeSpan.FromSeconds(30))
    {
      MessageQueue.ProcessMessage();
    }
  }
}
0 голосов
/ 17 ноября 2010

То, что вы хотите, это объект таймера синхронизации.В Win32 это называется таймером ожидания (к сожалению, требуется некоторое P / invoke, если я не ошибаюсь).

Вот что вы бы сделали:

  • Создание таймера ожидания (убедитесь, что это автоматический сброс).
  • Установите ожидаемый таймер с периодом 30 секунд.
  • Цикл:
  • WaitForSingleObject (ожидаемый таймер) с бесконечным временем ожидания.
  • Очередь обработки.

Если обработка занимает более 30 с, таймер будет оставаться установленным до тех пор, пока вы не вызовете для него WaitForSingleObject.Кроме того, если обработка занимает, например, 20 секунд, таймер будет сигнализироваться через 10 секунд.

0 голосов
/ 17 ноября 2010

метод OnStart

AutoResetEvent autoEvent = new AutoResetEvent(true);
    while (true)
    {
        autoEvent.WaitOne();
        Thread t = new Thread(MessageQueue.ProcessMessage);             
        t.Start(autoEvent);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...