Как предотвратить запуск очереди System.Timers.Timer в пул потоков?

Существует проблема со стандартным поведением System.Timers.Timer. Таймер поднимает истекшее событие с некоторым интервалом. Но когда время выполнения внутри обработчика событий 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) Это было вдохновлено: С# 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. Подробнее об этом можно найти здесь

Итак, на данный момент я предпочту первое решение.

Ответ 1

То, что я обычно делаю в этом случае, это остановить таймер в начале обработчика Elapsed и запустить его снова в конце. Таким образом, вы обрабатываете только один тик за раз.

UPDATE:

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

Ответ 2

Я бы сказал, просто остановите его, а затем запустите его после вашего длительного выполнения, как это.

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

Ответ 3

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

Ответ 4

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

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

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;
  }
}

Ответ 5

Поскольку ни один из ответов не является потокобезопасным, позвольте мне предложить следующее:

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, то обработчик быстро возвращается и тики не имеют очереди (в которых они использовали бы простой оператор блокировки). Вот определения для 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();