Синхронизация таймера для предотвращения наложения - PullRequest
17 голосов
/ 26 марта 2009

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

Как наиболее просто настроить таймер для запуска задачи каждые 30 секунд, не перекрывая при этом выполнение? (Я предполагаю, что System.Threading.Timer является правильным таймером для этой работы, но может ошибаться).

Ответы [ 6 ]

30 голосов
/ 26 марта 2009

Вы можете сделать это с таймером, но вам потребуется некоторая форма блокировки сканирования и обновления базы данных. Простого lock для синхронизации может быть достаточно для предотвращения нескольких запусков.

При этом, может быть, лучше запустить таймер ПОСЛЕ того, как операция завершена, и просто использовать его один раз, а затем остановить. Перезапустите его после следующей операции. Это даст вам 30 секунд (или N секунд) между событиями, без шансов наложения и блокировки.

Пример:

System.Threading.Timer timer = null;

timer = new System.Threading.Timer((g) =>
  {
      Console.WriteLine(1); //do whatever

      timer.Change(5000, Timeout.Infinite);
  }, null, 0, Timeout.Infinite);

Работать немедленно ..... Готово ... ждать 5 секунд .... Работать немедленно ..... Готово ... ждать 5 секунд ....

23 голосов
/ 26 марта 2009

Я бы использовал Monitor.TryEnter в вашем прошедшем коде:

if (Monitor.TryEnter(lockobj))
{
  try
  {
    // we got the lock, do your work
  }
  finally
  {
     Monitor.Exit(lockobj);
  }
}
else
{
  // another elapsed has the lock
}
15 голосов
/ 26 марта 2009

Я предпочитаю System.Threading.Timer для таких вещей, потому что мне не нужно проходить через механизм обработки событий:

Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000);

object updateLock = new object();
void UpdateCallback(object state)
{
    if (Monitor.TryEnter(updateLock))
    {
        try
        {
            // do stuff here
        }
        finally
        {
            Monitor.Exit(updateLock);
        }
    }
    else
    {
        // previous timer tick took too long.
        // so do nothing this time through.
    }
}

Вы можете устранить необходимость блокировки, сделав таймер однократным и перезапустив его после каждого обновления:

// Initialize timer as a one-shot
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite);

void UpdateCallback(object state)
{
    // do stuff here
    // re-enable the timer
    UpdateTimer.Change(30000, Timeout.Infinite);
}
2 голосов
/ 26 марта 2009

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

Thread updateDBThread = new Thread(MyUpdateMethod);

...

private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    if(!updateDBThread.IsAlive)
        updateDBThread.Start();
}
1 голос
/ 02 сентября 2015

Я использовал мьютекс, когда хотел одного исполнения:

    private void OnMsgTimer(object sender, ElapsedEventArgs args)
    {
        // mutex creates a single instance in this application
        bool wasMutexCreatedNew = false;
        using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew))
        {
            if (wasMutexCreatedNew)
            {
                try
                {
                      //<your code here>
                }
                finally
                {
                    onlyOne.ReleaseMutex();
                }
            }
        }

    }

Извините, что я так поздно ... Вам нужно будет указать имя мьютекса как часть вызова метода GetMutexName ().

1 голос
/ 26 марта 2009

Вы можете использовать AutoResetEvent следующим образом:

// Somewhere else in the code
using System;
using System.Threading;

// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);

void MyWorkerThread()
{
   while(1)
   {
     // Wait for work method to signal.
        if(autoEvent.WaitOne(30000, false))
        {
            // Signalled time to quit
            return;
        }
        else
        {
            // grab a lock
            // do the work
            // Whatever...
        }
   }
}

Немного более «умное» решение выглядит следующим образом в псевдокоде:

using System;
using System.Diagnostics;
using System.Threading;

// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);

void MyWorkerThread()
{
  Stopwatch stopWatch = new Stopwatch();
  TimeSpan Second30 = new TimeSpan(0,0,30);
  TimeSpan SecondsZero = new TimeSpan(0);
  TimeSpan waitTime = Second30 - SecondsZero;
  TimeSpan interval;

  while(1)
  {
    // Wait for work method to signal.
    if(autoEvent.WaitOne(waitTime, false))
    {
        // Signalled time to quit
        return;
    }
    else
    {
        stopWatch.Start();
        // grab a lock
        // do the work
        // Whatever...
        stopwatch.stop();
        interval = stopwatch.Elapsed;
        if (interval < Seconds30)
        {
           waitTime = Seconds30 - interval;
        }
        else
        {
           waitTime = SecondsZero;
        }
     }
   }
 }

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


Редактировать

Я должен добавить, что этот код предполагает, что у вас запущен только один из этих MyWorkerThreads (), в противном случае они будут выполняться одновременно.

...