2009-11-08 11 views
9

Ich habe eine Windows-Dienstanwendung, die eine Threading.Timer und eine TimerCallback verwendet, um einige Verarbeitung in bestimmten Intervallen zu tun. Ich muss diesen Verarbeitungscode auf nur 1 Thread gleichzeitig sperren.Verwenden der Sperre mit Threading.Timer

So zum Beispiel wird der Dienst gestartet und der erste Rückruf wird ausgelöst und ein Thread wird gestartet und beginnt mit der Verarbeitung. Dies funktioniert solange die Verarbeitung vor dem nächsten Callback abgeschlossen ist. Nehmen wir zum Beispiel an, dass die Verarbeitung etwas länger als gewöhnlich dauert und der TimerCallback erneut ausgelöst wird, während ein anderer Thread verarbeitet wird. Ich muss diesen Thread warten lassen, bis der andere Thread fertig ist.

Hier ist eine Probe von meinem Code:

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

Ist dies ein sicherer Weg, dies zu tun? Was passiert, wenn die Warteschlange ziemlich umfangreich wird? Gibt es eine bessere Option?

Antwort

26

Wenn es für Sie in Ordnung ist, dass die Ereignisse mit einem konstanten Intervall zwischen ihnen ausgelöst werden (im Gegensatz zum aktuellen Code, der sie in einem konstanten Intervall auslöst), können Sie den Timer ohne einen Zeitraum starten und jedes Mal anstehen ein neuer Rückruf, z

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

Dieser Code teilt dem neu erstellten Timer mit, dass er sofort und nur einmal ausgelöst wird. Im Bearbeitungscode erledigt er die Arbeit und weist dann den Timer an, in nur 10 Sekunden erneut zu feuern. Da der Zeitgeber jetzt nicht regelmäßig zündet, sondern durch seine Rückrufmethode neu gestartet wird, ist der Rückruf garantiert single-threaded ohne Warteschlange.

Wenn Sie ein konstantes Intervall beibehalten möchten, ist es etwas schwieriger, da Sie entscheiden müssen, was zu tun ist, wenn die Verarbeitung länger als das Timerintervall dauert. Eine Option besteht darin, das zu tun, was Sie gerade tun, aber das wird im Wesentlichen mit vielen wartenden Threads und eventuellen Thread-Pool-Problemen enden. Eine andere Option besteht darin, den Rückruf einfach zu verwerfen, wenn bereits ein Vorgang im Gange ist, z.

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

Nette Idee mit dem Auslösen der Timer nie darüber auf diese Weise gedacht. Das zweite Szenario bedeutet, dass der Thread einfach beendet wird und nicht wartet? – James

+0

Ja, im zweiten Szenario kehrt der TryEnter sofort zurück, wenn es einen anderen Thread gibt, der sich gerade im Verarbeitungscode befindet, also überspringt er das Timer-Intervall. –

+0

Dies sollte den Trick tun, danke. – James

1

Das Schlimmste, wenn die Verarbeitung Code mehr dauert passieren kann, als 10s ist auszuführen, dass Sie ein Threadpool-Thread verschwendet wird jedes Mal gibt es einen neuen Rückruf genannt (sie werden in der lock-Anweisung warten). Und wenn Sie alle Threadpool-Threads HttpWebRequest, ASP.NET, asynchrone Delegat-Aufrufe ... leiden.

Was ich tun würde ist, den ersten Rückruf sofort zu planen. Dann, wenn Sie wirklich Ihre DoSomething benötigen() bis alle 10 Sekunden aufgerufen werden:

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

Oder etwas entlang dieser Linie.