W .NET istnieje kilka klas, które odpowiadają mniej więcej za to samo. Przyczyna dlaczego nie ma po prostu jednej klasy a kilka, wynika z faktu, że cześć z nich była zaprojektowana z myślą o konkretnej technologii jak np. WinForms. Nie zmienia to faktu, że dla części programistów nie jest to jasne, kiedy używać konkretnego timer’a.
OK, to zaczynamy. W .NET mamy następujące timer’y:
- System.Threading.Timer – używamy, gdy chcemy aby nasza operacja była wykonywana w osobnym wątku (z puli). Dlatego, ten timer znajduje się w przestrzeni Threading, co jawnie mówi, że mamy do czynienia z wielowątkowością.
- System.Windows.Forms.Timer – callback zawsze wykonywany na wątku UI. Klasa przeznaczona dla WinForms i może zostać umieszczona na formatce.
- System.Windows.Threading.DispatcherTimer – odpowiednik powyższego timer’a dla WPF\Silverlight.
- Windows.UI.XAML.DispatcherTimer – tak samo jak 2 i 3 ale dla aplikacji WindowsStore.
- System.Timers.Timer – timer jest analogiczny do System.Threading.Timer tzn. wykonuje callback na osobnym wątku (z puli). Jaka jest więc różnica?
Główną różnicą jest SynchronizationObject, właściwość wyeksponowana przez System.Timers.Timer. Domyślnie, zdarzenie elapsed jest wykonywane na wątku z puli. Istnieje jednak możliwość, właśnie za pomocą SynchronizationObject, synchronizacji. Przekazując obiekt synchronizacyjny (np. dowolną formatkę w WinForms), Elapsed będzie wykonywany w kontekście wątku UI. Czy to oznacza, że efekt jest taki sam jak w przypadku System.Windows.Forms.Timer? Nie do końca…
Załóżmy, że chcemy zsynchronizować timer z formatką w WinForms:
System.Timers.Timer timer=new Timer(1000); timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); timer.SynchronizingObject = this; // this to formatka timer.Start();
Następnie gdzieś w kodzie wywołujemy Thread.Sleep na 5 sekund w UI Thread. W przypadku System.Timers.Timer, wszystkie wywołania będą kolejkowane i po 5 sekundach po prostu otrzymamy 5 callback’ów (Interval został ustawiony na jedną sekundę). System.Windows.Forms.Timer w momencie gdy nie może wywołać callbacka (bo np. ktoś wykonał Sleep i wątek nie jest zaplanowany) wtedy po prostu taki callback jest ignorowany.
SynchronizationObject to implementacja interfejsu ISynchronizeInvoke, który zawiera metodę Invoke. W momencie, gdy callback ma zostać wykonany to jest wywoływany właśnie przez SynchronizationObjct.Invoke. W WinForms, każda formatka implementuje ISynchronizationInvoke i może zostać użyta jako obiekt synchronizujący. Koncepcja WPF jednak znacząco się różni (ISynchronizationInvoke to pochodna z WinAPI) i okno w WPF nie implementuje tego interfejsu. Jeśli chcemy wykonać jakąś operację na wątku UI w WPF używamy DispatcherTimer.
Podsumowując, większość timerów zostało stworzonych z myślą o konkretnej technologii. Wyłącznie System.Threading.Timer jest całkowicie neutralny jeśli chodzi o używaną technologie oraz częściowo System.Timers.Timer (wyjątkiem jest SynchronizationObject).
Ponadto istnieje kilka małych różnic. Na przykład klasa System.Threading.Timer jest oznaczona jako sealed i z tego względu nie może oczywiście być rozszerzona. Threading.Timer również wspiera przekazanie stanu lub dueTime co oznacza opóźnienie zanim pierwszy raz callback zostanie wywołany (pozostałe timer’y tego nie potrafią). Powinniśmy jednak się kierować zawsze technologią z jakiej korzystamy (a może chcemy być niezależni od niej) a nie tymi drobnymi różnicami typu przekazanie stanu – z łatwością możemy to samodzielnie zaimplementować.