CONFRONTO TRA I
TRE TIMER DISPONIBILI Le classi timer di
System.Threading, System.Windows.Forms e System.Timers.
È piuttosto frequente la necessità di eseguire delle azioni a
intervalli di tempo ben specificati, e ovviamente quesot in .Net è
possibile, tuttavia si potrebbe rimanere inizialmente disorientati dalle
possibilità che il framework offre. Esistono infatti ben tre tipi di
timer differenti: System.Windows.Forms.Timer, System.Threading.Timer,
System.Timers.Timer. Vediamone le caratteristiche:
- System.Threading.Timer: questo timer è molto leggero ed
efficiente, permette di impostare una funzione da chiamare allo
scadere di ogni intervallo e di passargli un parametro; tuttavia è
eseguito su un thread indipendente da quello in cui è stato creato,
in particolare su uno di quelli disponibile nel ThreadPool di sistema;
ciò significa che l'utilizzo di questo timer porta con sé tutte le
complicazioni che la programmazione parallela comporta;
- System.Windows.Forms.Timer: timer particolarmente adatto per
eseguire processi temporizzati che riguardano l'interfaccia grafica
(è significativo che si trovi infatti in System.Windows.Forms)
che si
utilizza come un qualsiasi controllo (inseribile dalla Toolbox); allo scadere dell'intervallo
di tempo impostato viene invocato un evento Tick, con precisione per
valori superiori a 55 millisecondi; utilizza un solo thread,
inserendosi nella coda dei messaggi dell'interfaccia grafica, ciò
significa che se l'interfaccia grafica smette di rispondere anche
l'evento temporizzato non verrà eseguito;
- System.Timers.Timer: come il timer di System.Threading si basa
su un thread pool e per questo è maggiormente accurato di
System.Windows.Forms.Timer, ma al contrario del
primo è thread-safe,
ovvero permette di scegliere tramite la proprietà
SynchronizingObject in quale thread eseguire l'evento
Elapsed
(ovvero quello che indica che è scaduto l'intervallo); ad esempio se
si imposta la proprietà SynchronizingObject su un form o un
controllo, l'evento verrà sollevato nel thread che lo ha creato
evitando i tipici problemi del parallelismo;
Brevemente, un ThreadPool è un "bacino" di thread che l'applicazione
ha a disposizione e a cui può attingere; questi thread sono chiamati
worker-thread e svolgono vari compiti di sistema o imposti dall'utente
come in questo caso o ogni volta che si chiamano i metodi asincroni che
iniziano per Begin o End (es. BeginRead, EndConnect e così via). Ma
scendiamo ora nel dettaglio e vediamo come si possono utilizzare.
Vogliamo realizzare una semplicissima applicazione che scriva ogni 50
millisecondi un numero casuale in tre label tramite i tre possibili tipi
di timer visti.
Creiamo un nuovo progetto Windows Forms e prepariamo tutti i nostri
timer:
Public Class frmMain
Private tmrThread As Threading.Timer
Private tmrTimer As System.Timers.Timer
Private tmrWindowsForms As System.Windows.Forms.Timer
Private rnd As New Random()
Const intInterval As Integer = 50
Private Sub frmMain_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
tmrThread = New System.Threading.Timer(AddressOf NewRandomNumber, _
lblSystemThreading, 0, intInterval)
tmrTimer = New System.Timers.Timer(intInterval)
AddHandler tmrTimer.Elapsed, AddressOf tmrTimer_Elapsed
tmrTimer.SynchronizingObject = Me
tmrTimer.Start()
tmrWindowsForms = New System.Windows.Forms.Timer()
tmrWindowsForms.Interval = intInterval
AddHandler tmrWindowsForms.Tick, AddressOf tmrWindowsForms_Tick
tmrWindowsForms.Start()
End Sub
Private Sub frmMain_FormClosed(ByVal sender As Object, _
ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
Dim ewh As New System.Threading.EventWaitHandle(True, _
Threading.EventResetMode.AutoReset)
tmrThread.Dispose(ewh)
ewh.WaitOne()
tmrWindowsForms.Stop()
tmrTimer.Stop()
End Sub
[...]
End Class
Prima di tutto abbiamo dichiarato i tre tipi di timer, un generatore
di numeri casuali (che verrà usato nella funzione di callback) e una
costante per indicare i millisecondi di intervallo per tutti i timer.
In frmMain_Load, per semplicità, impostiamo la proprietà
Control.CheckForIllegalCrossThreadCalls su False, in modo da non
ricevere errori quando effettuiamo chiamate su controlli del form da
thread diversi da quello da cui sono state create. Ciò succede solamente
con il timer di System.Threading, che come abbiamo detto non è
thread-safe. In realtà bisognerebbe utilizzare le funzioni BeginInvoke e
EndInvoke di un controllo per effettuare questo tipo di modifiche, ma
per brevità tralasciamo questo dettaglio (nelle applicazioni pratiche
non trascurabile). In seguito creiamo
un'istanza di System.Threading.Thread al quale passiamo il riferimento
ad una funzione, un argomento che essa riceverà, il tempo dopo il quale
avviare il timer e l'intervallo con il quale invocare il delegato.
Per System.Timers.Timer è tutto abbastanza intuitivo, se non l'uso della
proprietà SynchronizingObject che impostiamo sul form stesso in modo che
l'evento Elapsed, in questo caso la funzione tmrTimer_Elapsed, sia
invocata nello stesso thread del controllo evitando così i problemi di
dover effettuare chiamate cross-thread (questo timer infatti non darebbe
problemi anche se CheckForIllegalCrossThreadCalls fosse impostato su
True). Per System.Windows.Forms.Timer, non vi è nulla di nuovo.
Nella Sub frmMain_FormClosed ci preoccupiamo di interrompere tutti i
nostri timer, in particolare per quello di Threading dobbiamo
assicurarci che sia terminata l'ultima esecuzione prima di chiudere il
form altrimenti, potrebbe tentare di accedere ad un controllo non più
disponibile.
|