Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

Thread updateDBThread = new Thread(MyUpdateMethod);

...

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

Ответ 5

Вы можете использовать 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;
        }
     }
   }
 }

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


Edit

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

Ответ 6

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

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