2009-11-08 2 views
9

У меня есть приложение Windows Service, которое использует Threading.Timer и TimerCallback для выполнения определенной обработки с определенными интервалами. Мне нужно заблокировать этот код обработки только по одному потоку за раз.Использование блокировки с Threading.Timer

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

Вот пример моего кода:

static Timer timer; 
static object locker = new object(); 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, 10000); 
} 

public void DoSomething() 
{ 
     lock(locker) 
     { 
      // my processing code 
     } 
} 

Является ли это безопасный способ сделать это? Что произойдет, если очередь станет достаточно существенной? Есть ли лучший вариант?

ответ

26

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

static Timer timer; 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, Timeout.Infinite); 
} 

public void DoSomething() 
{ 
     try 
     { 
      // my processing code 
     } 
     finally 
     { 
      timer.Change(10000, Timeout.Infinite); 
     } 
} 

Этот код сообщает, что только что созданный таймер запускается немедленно, только один раз. В коде обработки он выполняет работу, а затем сообщает таймеру снова срабатывать за 10 секунд, только один раз. Поскольку таймер теперь не запускается периодически, но снова запускается его методом обратного вызова, то обратный вызов гарантированно будет однопоточным без очереди.

Если вы хотите сохранить постоянный интервал, то это немного сложнее, так как вам нужно решить, что делать, если обработка начинает длиться дольше, чем интервал таймера. Один из вариантов заключается в том, чтобы делать то, что вы сейчас делаете, но это, в сущности, приведет к большому количеству помещенных в очередь потоков и возможному голоданию пула потоков. Другой вариант - просто отказаться от обратного вызова, если он уже выполняется, например.

static Timer timer; 
static object locker = new object(); 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, 10000); 
} 

public void DoSomething() 
{ 
     if (Monitor.TryEnter(locker)) 
     { 
      try 
      { 
       // my processing code 
      } 
      finally 
      { 
       Monitor.Exit(locker); 
      } 
     } 
} 
+0

Хорошая идея с запуском таймеров никогда не задумывалась об этом. Второй сценарий, означает ли это, что поток просто выходит и не ждет? – James

+0

Да, во втором сценарии TryEnter возвращается немедленно, если есть еще один поток, который в настоящее время находится в коде обработки, поэтому он эффективно пропускает этот интервал таймера. –

+0

Это должно сделать трюк, спасибо. – James

1

Самое худшее, что может произойти, если код обработки занимает более 10 секунд, чтобы выполнить то, что вы будете тратить 1 Threadpool нить каждый раз, когда есть новый обратный вызов называется (они будут ждать в заявлении замка). И если вы возьмете все потоки threadpool HttpWebRequest, ASP.NET, асинхронные вызовы делегатов ... пострадают.

Что бы я сделал, это немедленно запланировать первый обратный вызов. Тогда, если вам действительно нужна ваша DoSomething() называться каждые 10s:

public void DoSomething() 
{ 
     DateTime start = DateTime.UtcNow; 
     ... 
     TimeSpan elapsed = (DateTime.UtcNow - start); 
     int due_in = (int) (10000 - elapsed.TotalMilliseconds); 
     if (due_in < 0) 
      due_in = 0; 
     timer.Change (due_in, Timeout.Infinite); 
} 

Или что-то вдоль этой линии.

Смежные вопросы