如何防止System.Timers.Timer在线程池上排队执行?

时间:2011-02-02 15:38:10

标签: c# .net vb.net multithreading timer

标准的System.Timers.Timer行为存在问题。计时器以一定间隔提升Elapsed事件。但是当Elapsed事件处理程序内的执行时间超过计时器间隔时,线程池开始排队事件处理。这是我的问题。这是因为使用我的Elapsed事件处理程序,我从数据库中获取一些数据并使用它做一些事情,最后将结果保存回数据库。但是数据处理应该只提供一次。那么,有没有办法防止排队System.Timers.Timer的过时事件。

作为此问题的插图,您可以考虑下一个测试程序:

public class EntryPoint
{

    private static void TimeProc(object state, ElapsedEventArgs e)
    {
        Console.WriteLine("Current time {0} on the thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(20000);
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Press <Enter> for finishing\n\n");
        ThreadPool.SetMaxThreads(10, 10);
        System.Timers.Timer MyTimer = new System.Timers.Timer(1000);
        MyTimer.Elapsed += new ElapsedEventHandler(TimeProc);
        MyTimer.Start();
        Console.ReadLine();
        MyTimer.Stop();
    }
}

可能的输出如下:

Current time 03.02.2011 0:00:09 on the thread 4
Current time 03.02.2011 0:00:10 on the thread 5
Current time 03.02.2011 0:00:12 on the thread 6
Current time 03.02.2011 0:00:13 on the thread 7
Current time 03.02.2011 0:00:14 on the thread 8
Current time 03.02.2011 0:00:15 on the thread 9
Current time 03.02.2011 0:00:16 on the thread 10
Current time 03.02.2011 0:00:17 on the thread 11
Current time 03.02.2011 0:00:18 on the thread 12
Current time 03.02.2011 0:00:19 on the thread 13
Current time 03.02.2011 0:00:30 on the thread 4
Current time 03.02.2011 0:00:30 on the thread 5

可能的解决方案:

1)它的灵感来自:C# Timer vs Thread in Service

并且有关于上述示例的代码:

    public class EntryPoint
    {
        private static System.Timers.Timer MyTimer;
        private static void TimeProc(object state, ElapsedEventArgs e)
        {
            Console.WriteLine("Current time {0} on the thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(20000);
            MyTimer.Enabled = true;
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Press <Enter> for finishing\n\n");
            ThreadPool.SetMaxThreads(10, 10);
            MyTimer = new System.Timers.Timer(1000);
            MyTimer.AutoReset = false;

            MyTimer.Elapsed += new ElapsedEventHandler(TimeProc);
            MyTimer.Enabled = true;
            Console.ReadLine();

        }
    }

2)第二种方式是关于SynchronizingObject,但它仅对Windows表单应用程序有价值,或者需要额外开发代码来实现将实现ISynchronizeInvoke接口的对象。有关这种方式的更多信息,您可以找到here

所以,现在我更愿意先解决。

5 个答案:

答案 0 :(得分:15)

在这种情况下我通常做的是在Elapsed处理程序的开头停止计时器并在结束时再次启动计时器。这样,您一次只能处理一个滴答。

<强>更新

根据MSDN链接,我认为他们的意思是你可以设置自己的标志(但仍然有勾号),但也应采取线程安全措施。

答案 1 :(得分:4)

我会说只是停下来然后在这么长时间执行之后启动它。

tmr.Stop();
//Your lengthy execution code goes here
tmr.Start();

答案 2 :(得分:4)

您所看到的行为是设计上的。在计时器上设置SynchronizingObject,或使用在多个线程上不勾选的其他计时器(例如System.Threading.Timer)。

答案 3 :(得分:2)

我只是创建一个静态标志变量。这样我的计时器就会继续运行,但如果方法在下一个计时器周期之前没有完成,则只需绕过代码。

在用于计时器的方法中,测试事件是否正在进行中。

Timer_Method_Called()
{
  if (eventInProgress == 0)
  {
     // flag event as in progress
     eventInProcess == 1;

     // perform code....

     // after code is complete, allow the method to execute
     eventInProgress == 0;
  }
}

答案 4 :(得分:2)

由于没有一个答案是线程安全的,让我提出一个:

void oneHundredMS_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {

  if (setTimerBodyRunning()) { //only proceed to body if it is not already processing; setTimerBodyRunning must be thread-safe
    // here you do your long running operation
    setTimerBodyFinished();
  }
}

如您所见,计时器处理程序首先检查它是否尚未运行,并且只有在返回false时才进入正文。如果返回true,则处理程序快速返回并且ticks不排队(他们将使用简单的锁定语句)。以下是setTimerBodyRunning和setTimerBodyFinished的定义:

private bool setTimerBodyRunning() {
        bool retVal = false;
        lock (timerBodyRunning) { //timerBodyRunning is type object and it holds a bool.  
            //The reason it is object and not bool is so it can be locked on to ensure thread safety
            if (!((bool)timerBodyRunning)) {
                timerBodyRunning = true;
                retVal = true;
            }
        }
        return retVal;
    }

private void setTimerBodyFinished() {
    lock (timerBodyRunning) {
        timerBodyRunning = false;
    }
}

以下是初始化和启动计时器的方法:

object timerBodyRunning = new object();
timerBodyRunning = false;
System.Timers.Timer timerFrequency100MS = new System.Timers.Timer();
timerFrequency100MS.Interval = FREQUENCY_MS; //it will fire every 100 milliseconds
timerFrequency100MS.Elapsed += new System.Timers.ElapsedEventHandler(oneHundredMS_Elapsed);
timerFrequency100MS.Start();