Таймер для опроса службы Windows

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

  1. Elapsed EventHanler работает в фоновом режиме, поэтому его выполнение будет прервано, если основной поток завершится. Я хотел, чтобы функция System.Timers.Timer.Stop блокировала основной поток до завершения выполнения Elapsed EventHanler.
  2. System.Timers.Timer не имеет дело с повторным входом события. Я хочу, чтобы интервал находился между двумя Elapsed EventHanler, чтобы таймер никогда не вызывал Elapsed EventHanler, если предыдущий вызов (+ интервал) еще не завершился.

При написании класса я обнаружил, что мне нужно будет решить некоторые проблемы, связанные с потоковой передачей, и, поскольку я не слишком опытен в этом, я хочу знать, является ли следующий класс Timer потокобезопасным?

public class Timer
{
    System.Timers.Timer timer = new System.Timers.Timer() { AutoReset = false };
    ManualResetEvent busy = new ManualResetEvent(true);

    public double Interval
    {
        get { return timer.Interval; }
        set { timer.Interval = value; }
    }

    public Timer()
    {
        timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
    }

    void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            busy.Reset();
            OnElapsed(e);
            timer.Start();
        }
        finally
        {
            busy.Set();
        }
    }

    public event EventHandler Elapsed;

    protected void OnElapsed(EventArgs e)
    {
        if (Elapsed != null)
        {
            Elapsed(this, e);
        }
    }

    public virtual void Start()
    {
        busy.WaitOne();
        timer.Start();
    }

    public virtual void Stop()
    {
        busy.WaitOne();
        timer.Stop();
    }
} 

person Martin    schedule 13.11.2009    source источник


Ответы (4)


Прежде всего, вы можете использовать System.Threading.Timer вместо этого таймера, так как, согласно моему опыту, это более эффективный таймер (просто совет по личному опыту).

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

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

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

Пример кода, который я написал для такой ситуации (код методов был удален, это даст вам детали дизайна).

namespace SMSPicker
{
 public partial class SMSPicker : ServiceBase{
    SendSMS smsClass;
    AutoResetEvent autoEvent;
    TimerCallback timerCallBack;
    Timer timerThread;
    public SMSPicker()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        // TODO: Add code here to start your service.
        smsClass = new SendSMS();
        autoEvent = new AutoResetEvent(false);
        long timePeriod = string.IsNullOrEmpty(ConfigurationSettings.AppSettings["timerDuration"]) ? 10000 : Convert.ToInt64(ConfigurationSettings.AppSettings["timerDuration"]);
        timerCallBack = new TimerCallback(sendSMS);
        timerThread = new Timer(timerCallBack, autoEvent, 0, timePeriod);
    }


    private void sendSMS(object stateInfo)
    {
        AutoResetEvent autoResetEvent = (AutoResetEvent)stateInfo;
        smsClass.startSendingMessage();
        autoResetEvent.Set();
     }

    protected override void OnStop()
    {
        // TODO: Add code here to perform any tear-down necessary to stop your service.
        smsClass.stopSendingMessage();
        timerThread.Dispose();            

    }
}
}







namespace SMSPicker
{
class SendSMS
{
    //This variable has been done in order to ensure that other thread does not work till this thread ends
    bool taskDone = true;
    public SendSMS()
    {

    }

    //this method will start sending the messages by hitting the database
    public void startSendingMessage()
    {

        if (!taskDone)
        {
            writeToLog("A Thread was already working on the same Priority.");
            return;
        }

        try
        {
        }
        catch (Exception ex)
        {
            writeToLog(ex.Message);
        }
        finally
        {
            taskDone = stopSendingMessage();

            //this will ensure that till the database update is not fine till then, it will not leave trying to update the DB
            while (!taskDone)//infinite looop will fire to ensure that the database is updated in every case
            {
                taskDone = stopSendingMessage();
            }
        }

    }


public bool stopSendingMessage()
    {
        bool smsFlagUpdated = true;
        try
        {

        }
        catch (Exception ex)
        {
            writeToLog(ex.Message);
        }
        return smsFlagUpdated;
    }

}
}
person Ashish Jain    schedule 13.11.2009

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

открытый класс PollingService { частный поток _workerThread; частный AutoResetEvent _finished; частная константа int _timeout = 60*1000;

public void StartPolling()
{
    _workerThread = new Thread(Poll);
    _finished = new AutoResetEvent(false);
    _workerThread.Start();
}

private void Poll()
{
    while (!_finished.WaitOne(_timeout))
    {
        //do the task
    }
}

public void StopPolling()
{
    _finished.Set();
    _workerThread.Join();
}

}

В вашей службе

public partial class Service1 : ServiceBase
{
    private readonly PollingService _pollingService = new PollingService();
    public Service1()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        _pollingService.StartPolling();
    }

    protected override void OnStop()
    {
        _pollingService.StopPolling();
    }

}
person Mou    schedule 06.05.2015

У меня была похожая проблема: проще было бы использовать System.Threading.Timer с периодом = 0;

В этом сценарии метод обратного вызова вызывается один раз. Затем сброс таймера (вызов его метода Change()) снова включает его; в конце вашего повторяющегося метода.

Этот сценарий объясняется здесь: http://kristofverbiest.blogspot.com/2008/08/timers-executing-task-at-regular.html

person Community    schedule 23.11.2009

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

Вот очень общий шаблон, который вы можете использовать для начала.

public class YourService : ServiceBase
{
  private CancellationTokenSource cts = new CancellationTokenSource();
  private Task mainTask = null;

  protected override void OnStart(string[] args)
  {
    mainTask = new Task(Poll, cts.Token, TaskCreationOptions.LongRunning);
    mainTask.Start();
  }

  protected override void OnStop()
  {
    cts.Cancel();
    mainTask.Wait();
  }

  private void Poll()
  {
    CancellationToken cancellation = cts.Token;
    TimeSpan interval = TimeSpan.Zero;
    while (!cancellation.WaitHandle.WaitOne(interval))
    {
      try 
      {
        // Put your code to poll here.
        // Occasionally check the cancellation state.
        if (cancellation.IsCancellationRequested)
        {
          break;
        }
        interval = WaitAfterSuccessInterval;
      }
      catch (Exception caught)
      {
        // Log the exception.
        interval = WaitAfterErrorInterval;
      }
    }
  }
}
person Mou    schedule 06.05.2015