Реентерабельный таймер в службе Windows

Я хочу создать службу Windows, которая должна выполнять разные методы в разное время. Дело вовсе не в точности. Я использую system.timers.timer и регулирую различные методы, которые будут выполняться в методе Eventhandler со счетчиками. Пока все работает.

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

Упрощенный до одного метода, мой метод elapsedEventHandler выглядит примерно следующим образом (try-catch и различные методы исключены здесь)

Примечание. Хотя это отлично работает на моей Win7 x64, на машине с Win7 x86 с установленным почти таким же программным обеспечением возникают проблемы всякий раз, когда выполняемый метод занимает много времени. Таймер больше не тикает, исключение не выдается. Ничего! теперь мой вопрос: правильно ли я делаю часть с контролем доступа и таймером, чтобы я мог сосредоточиться на других вещах? Я просто не знаком с таймерами и особенно с потоками.

     private static int m_synchPoint=0;
     private System.Timers.Timer timerForData = null;

    public MyNewService()
    {

        timerForData = new System.Timers.Timer();
        timerForData.Interval = 3000;
        timerForData.Elapsed += new ElapsedEventHandler(Timer_tick);
    }
    //Initialize all the timers, and start them
    protected override void OnStart(string[] args)
    {

        timerForData.AutoReset = true;
        timerForData.Enabled = true;
        timerForData.Start();
    }

    //Event-handled method
    private void Timer_tick(object sender, System.Timers.ElapsedEventArgs e)
    {
            ////safe to perform event - no other thread is running the event?                      
            if (System.Threading.Interlocked.CompareExchange(ref m_synchPoint, 1, 0) == 0)

            {
             //via different else-ifs basically always this is happening here, except switching aMethod,bMethod...
             processedevent++; 
             Thread workerThread = new Thread(aMethod);
             workerThread.Start();
             workerThread.Join(); 
             m_synchPoint=0;
             }
             else
             {
              //Just dismiss the event
              skippedevent++;
             }
     }   

Заранее большое спасибо!
Мы очень признательны за любую помощь!


person Timo Jak    schedule 10.12.2011    source источник
comment
Я не понимаю, как опубликованный код может воспроизвести эту проблему.   -  person Hans Passant    schedule 11.12.2011
comment
На самом деле я НЕ МОГУ воспроизвести проблему на своей машине, только на машинах, на которых я тестирую, где я не могу отлаживать в Visual Studio. Главный вопрос здесь заключается в следующем: правильно ли в приведенном выше коде используются таймеры и функция Compare Exchange, чтобы можно было предположить, что одновременно работает только ОДИН метод? Еще: Как бы вы реализовали такой сценарий?   -  person Timo Jak    schedule 11.12.2011
comment
Это не основная проблема, но не используйте static для данных, которые должны быть специфичными для экземпляра (как в данном случае должно быть m_synchPoint).   -  person bobbymcr    schedule 11.12.2011
comment
Почему вы звоните workerThread.Start, а затем сразу workerThread.Join? Нет смысла делать что-то таким образом, поскольку поток таймера просто будет ждать, пока не завершится новый поток. Выполните aMethod напрямую и избавьте себя от накладных расходов на запуск нового потока.   -  person Jim Mischel    schedule 11.12.2011
comment
JimMischel: Читая мой log.debug, я понял, что это не так, что меня тоже удивило! Это вообще возможно? Моя первоначальная мысль заключалась в том, чтобы явно посоветовать сервису дождаться завершения... @bobbymcr: Да, конечно, но это не исправление =(   -  person Timo Jak    schedule 11.12.2011
comment
Я думаю, вы неправильно поняли. Вместо того, чтобы создавать поток для выполнения aMethod, запустите поток и дождитесь его завершения, полностью исключите поток и просто вызовите aMethod напрямую.   -  person Jim Mischel    schedule 11.12.2011
comment
Я не ясно выразился, я думаю... Я ДЕЙСТВИТЕЛЬНО понимаю, что вы говорите. Я имел в виду, что, когда я сделал это так, как вы предлагаете, я увидел из моего файла отладки, что Timer_tick не будет ждать завершения метода, прежде чем он завершит себя. Это вообще возможно? Это заставило меня включить новую ветку в первую очередь... чтобы иметь возможность явно сказать "подождите".   -  person Timo Jak    schedule 11.12.2011
comment
Если вы вызываете aMethod непосредственно из тика таймера, такт таймера не завершится до выхода aMethod. В конце концов, это всего лишь код. Если вы вызываете метод в потоке, даже в такт таймера, этот метод должен завершиться до того, как вызывающая сторона сможет продолжить работу.   -  person Jim Mischel    schedule 12.12.2011


Ответы (4)


Если вы хотите просто пропустить вызов метода, пока предыдущий метод не завершился, просто используйте Monitor.TryEnter(lockObject) перед вызовом вашего метода.

РЕДАКТИРОВАТЬ: Вот пример -

public class OneCallAtATimeClass
{

    private object syncObject;

    public TimerExample()
    {
      syncObject = new object();
    }

    public void CalledFromTimer()
    {    
      if (Monitor.TryEnter(syncObject);)
      {
        try
        {
          InternalImplementation();
        }
        finally
        {
          Monitor.Exit(syncObject);
        }
      }    
    }

    private void InternalImplementation()
    {
      //Do some logic here
    }

  }
person Maxim    schedule 10.12.2011
comment
Точно моя мысль. Вы можете добавить пример, показывающий вызов TryEnter в начале метода и правильный try...finally, вызывающий Monitor.Exit по завершении. - person Jim Mischel; 11.12.2011
comment
@JimMischel - конечно, добавил пример - person Maxim; 11.12.2011

Я бы рекомендовал использовать System.Threading.Timer для этой функциональности. Вы можете отключить таймер, когда он выполняется, обработать ваши данные, а затем снова включить таймер.

РЕДАКТИРОВАТЬ:

Я думаю, что имеет больше смысла использовать System.Threading.Timer, потому что на самом деле нет причин, по которым вам нужно сбрасывать таймер на поверхность дизайна, что является почти единственной причиной для использования System.Timers.Timer. Я действительно хотел бы, чтобы MS все равно удалила его, это обертка System.Threading.Timer, которую не так уж сложно использовать в первую очередь.

Да, вы рискуете проблемой повторного входа, поэтому я указал изменить время ожидания на Timeout.Infinite. У вас не будет этой проблемы с повторным входом, если вы создадите таймер с Timeout.Infinite.

public class MyClass
{
    private System.Threading.Timer _MyTimer;

public MyClass()
{
    _MyTimer = new Timer(OnElapsed, null, 0, Timeout.Infinite);
}

public void OnElapsed(object state)
{
    _MyTimer.Change(Timeout.Infinite, Timeout.Infinite);
    Console.WriteLine("I'm working");
    _MyTimer.Change(1000, Timeout.Infinite);
}

}

person Bryan Crosby    schedule 10.12.2011
comment
Но вы также можете отключить System.Timers.Timer. Timer.Enabled = false. - person Jim Mischel; 11.12.2011
comment
плюс я читал, что timerevents можно вызвать даже после того, как таймер был остановлен? По этой причине я использую ссылку CompareExchange. - person Timo Jak; 11.12.2011
comment
@JimMischel: немного уточнил мой ответ. - person Bryan Crosby; 11.12.2011
comment
@Bryan: я тоже не рекомендую использовать System.Timers.Timer, потому что он поглощает исключения, которые скрывают ошибки. Тем не менее, упаковка полезна по двум причинам: 1) она использует модель событий, а не обратный вызов. Модель событий в целом более знакома программистам .NET. Во-вторых, если вы используете его в приложении Windows Forms, установка SynchronizingObject для формы вызывает вызов обработчика событий в потоке графического интерфейса, избавляя вас от необходимости использовать InvokeRequired и Invoke для доступа к элементам управления. - person Jim Mischel; 12.12.2011
comment
@Timo: Да, возможно, обратный вызов таймера (или событие) может быть вызван после того, как таймер был остановлен. Но это может произойти только в том случае, если следующий тик таймера произойдет до того, как обратный вызов предыдущего тика остановит таймер. Для этого вам понадобится очень быстрый таймер или очень медленный отклик. - person Jim Mischel; 12.12.2011
comment
@ Джим: баллы сняты. Я только подчеркнул это в этом сценарии, поскольку он сказал, что создает службу Windows. - person Bryan Crosby; 12.12.2011
comment
@BryanCrosby: Спасибо за редактирование. Похоже, единственная веская причина, по которой я использую timers.timer, заключается в том, что я не знал способов реализации threading.timer. Потому что, как я уже сказал, это WindowsService, а не графический интерфейс. Я получил это, работая с методом монитора выше, но в любом случае я собираюсь изменить таймер, повозиться и посмотреть, что я получаю. Спасибо! - person Timo Jak; 14.12.2011

Вы можете попробовать это:

Когда таймер сработает, отключите таймер.

Когда задача будет завершена, снова включите таймер... возможно, в предложении «Окончательно».

person Steve Wellens    schedule 10.12.2011

Вы правильно используете CompareExchange для проверки и устанавливаете поле m_synchPoint при начальной проверке. Вы неправильно используете прямое присваивание для сброса значения до 0 в конце метода. Вместо этого следует использовать Interlocked.Exchange, чтобы сбросить значение до 0. Как примечание, вы также должны изменить m_synchPoint на поле экземпляра - оно не должно быть статическим.

person bobbymcr    schedule 10.12.2011
comment
Я бы сказал полезно, но я не могу xD - person Timo Jak; 11.12.2011