Синхронизация таймера для предотвращения дублирования

StackOverflow https://stackoverflow.com/questions/684200

Вопрос

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

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

Это было полезно?

Решение

Вы можете сделать это с помощью таймера, но вам потребуется какая-то форма блокировки сканирования и обновления базы данных.Просто 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 секунд....

Другие советы

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

if (Monitor.TryEnter(lockobj))
{
  try
  {
    // we got the lock, do your work
  }
  finally
  {
     Monitor.Exit(lockobj);
  }
}
else
{
  // another elapsed has the lock
}

я предпочитаю 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);
}

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

Thread updateDBThread = new Thread(MyUpdateMethod);

...

private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    if(!updateDBThread.IsAlive)
        updateDBThread.Start();
}

Вы можете использовать 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(), иначе они будут работать одновременно.

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

    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().

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top