Kolumne: .NETversum

Den Takt vorgeben mit dem PeriodicTimer

Den Takt vorgeben mit dem PeriodicTimer

Kolumne: .NETversum

Den Takt vorgeben mit dem PeriodicTimer


Es gab schon vor .NET 6.0 mehrere verschiedene Zeitgeberklassen in .NET:

  • System.Threading.Timer (seit .NET Core 1.0 auch im modernen .NET)

  • System.Timers.Timer (seit .NET Core 2.0 auch im modernen .NET)

  • System.Windows.Threading.DispatcherTimer (seit .NET Core 3.0 auch im modernen .NET)

  • System.Windows.Forms.Timer (seit .NET Core 3.0 auch im modernen .NET)

  • System.Web.UI.Timer (nur im klassischen .NET Framework)

Nun kam in .NET 6.0 eine weitere Zeitgeberklasse hinzu: PeriodicTimer [1]. Diese Klasse liegt (wie die oben zuerst genannte Klasse Timer) im Namensraum System.Threading.

WaitForNextTickAsync()

Die Klasse PeriodicTimer erwartet im Konstruktor zwingend ein TimeSpan-Objekt, welches das Auslöseintervall vorgibt. Die Klasse besitzt (neben den von System.Object geerbten Methoden) sowie Dispose() nur eine eigene Methode: WaitForNextTickAsync(Cancella-tionToken). Diese Methode pausiert den aktuellen Thread jeweils so lange, bis das Intervall abgelaufen ist. Die WaitForNextTickAsync() liefert als Rückgabewert true, solange der Timer weiterlaufen soll. Das Grundkonstrukt ist also:

var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync())
{
  // Code
}

Die Methode WaitForNextTickAsync() darf aber zu einem Zeitpunkt nur einmal aufgerufen werden, also nicht mehrfach in mehreren Threads. Der Parameter CancellationToken ist optional. Mit ihm kann man den Zeitgeber von außen beenden. Durch den Aufruf von Dispose() kann man den Zeitgeber innerhalb der Schleife beenden. Beides zeigen das aussagekräftige Listing 1 und die entsprechende Ausgabe (Abb. 1).

Listing 1: PeriodicTimer im Einsatz

using System.Globalization;
using ITVisions;
 
namespace NET6Console;
public class NET6_PeriodicTimer_Demo
{
 
  public void Run()
  {
    CUI.H1(nameof(NET6_PeriodicTimer_Demo));
 
    var Intervall = TimeSpan.FromSeconds(1);
    Console.WriteLine("Starte Timer um: " + DateTime.Now.ToLongTimeString());
    Console.WriteLine("mit Intervall: " + Intervall.ToString());
    Console.WriteLine();
 
    var ctoken = new CancellationTokenSource();
 
#pragma warning disable CS4014 // hier bewusst kein await vor Task.// Run(), wir wollen, dass der Code unten weiterläuft
    Task.Run(async () =>
    {
      var count = 0;
      var timer = new PeriodicTimer(Intervall);
 
      do
      {
        count++;
        Console.WriteLine(count + ". Durchlauf startet um: " + 
         DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture));
 
        // tue irgendwas, was zwischen 1000 und 3000 Millisekunden dauert
        var dauerMillisekunden ...