用于轮询窗口服务的计时器

时间:2009-11-13 11:52:53

标签: c# .net multithreading timer

我编写了一个Timer类,以便在轮询另一个系统的Windows服务中使用它。我这样做是因为我有两个System.Timers.Timer没有解决的问题。

  1. Elapsed EventHanler在后台运行,因此如果主线程结束,它的执行将中止。我希望System.Timers.Timer.Stop函数阻止主线程,直到Elapsed EventHanler的执行结束。
  2. System.Timers.Timer不处理事件重入。我希望Interval介于两个Elapsed EventHanler之间,这样如果前一个调用(+ interval)还没有完成,Timer就不会调用Elapsed EventHanler。
  3. 在编写课程时,我发现我需要解决一些与thrading相关的问题,因为我不太熟悉那些我想知道以下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();
        }
    } 
    

4 个答案:

答案 0 :(得分:5)

首先,根据我的经验,你可以使用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;
    }

}
}

答案 1 :(得分:0)

我遇到了类似的问题:最简单的方法是使用System.Threading.Timer,句点= 0;

在这种情况下,调用一次回调方法。然后,重置计时器(调用其Change()方法)再次打开它;在你重新出现的方法结束时。

此处解释了此方案:http://kristofverbiest.blogspot.com/2008/08/timers-executing-task-at-regular.html

答案 2 :(得分:0)

另一种方法是等待事件,而不是使用计时器。

公共类PollingService {     private Thread _workerThread;     私有AutoResetEvent _finished;     private const 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();
    }

}

答案 3 :(得分:0)

您可以使用计时器或睡眠的专用线程/任务。就个人而言,我发现专用线程/任务比这些事情的计时器更容易使用,因为它更容易控制轮询间隔。此外,您绝对应该使用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;
      }
    }
  }
}