【问题标题】:Timer for a polling windows service轮询窗口服务的计时器
【发布时间】:2009-11-13 11:52:53
【问题描述】:

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

  1. Elapsed EventHanler 在后台运行,因此如果主线程结束,它的执行将中止。我希望 System.Timers.Timer.Stop 函数阻塞主线程,直到 Elapsed EventHanler 的执行结束。
  2. System.Timers.Timer 不处理事件重入。我希望间隔在两个 Elapsed EventHanler 之间,这样如果前一个调用 (+interval) 尚未完成,计时器将永远不会调用 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();
    }
} 

【问题讨论】:

    标签: c# .net multithreading timer


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      public class PollingService
      {
          private Thread _workerThread;
          private 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】:

        我有一个类似的问题:更简单的方法是使用 System.Threading.Timer 和 period=0;

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

        这里解释了这个场景:http://kristofverbiest.blogspot.com/2008/08/timers-executing-task-at-regular.html

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            猜你喜欢
            • 2016-08-21
            • 1970-01-01
            • 2021-12-29
            • 2013-05-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多