|
- Come sincronizzare il lavoro tra
due o più thread tramite Monitor -
|
|||
| COSA SERVE PER QUESTO TUTORIAL | |||
| Download | Chiedi sul FORUM | Glossario | cognizioni basiche di VB .Net sul threading | ||
| Sincronia tra thread con Monitor.Wait e Monitor.Pulse | |||
PROBLEMA: SOSPENDERE UN'OPERAZIONE TEMPORANEAMENTE Approcci per interrompere e riattivare un thread.
Nel precedente articolo avevamo introdotto l'oggetto
Monitor e avevamo
visto come il suo utilizzo poteva essere sostituito da un blocco SyncLock. In questo tutorial ci occuperemo sempre dell'oggetto
Monitor,
ma prestando attenzione ai metodi di sincronizzazione Wait,
Pulse e
PulseAll. Grazie ad essi è possibile mettere in attesa un thread (Monitor.Wait)
fino a quando un altro thread non gli comunica che può riprendere
l'esecuzione (Monitor.Pulse).
Public Sub Counter()
Dim C As Decimal = 0
Do
C += 1
If C = Decimal.MaxValue Then C = 0
Loop
Console.WriteLine("Thread terminato")
End Sub
Facciamo poi un piccolo sistema per avviare e controllare il thread che riceve tre parametri da linea di comando per uscire (quit), interrompere (pause) e riprendere (resume) l'esecuzione:
Dim syncObject As New Object()
Dim pauseFlag As Boolean = False
Dim stopFlag As Boolean = False
Sub SingleThread()
Dim counterThread As New Thread(AddressOf Counter)
counterThread.Start()
Dim command As String
command = Console.ReadLine()
Do While command <> "quit"
Select Case command
Case "pause"
' ...
Case "resume"
' ...
Case Else
Console.WriteLine("Unknown command")
End Select
command = Console.ReadLine()
Loop
' ...
End Sub
Per effettuare la sincronizzazione ci serviremo di tre variabili:
Vediamo dunque come implementare questo comportamento nel thread di Counter:
Public Sub Counter()
Dim C As Decimal = 0
Do
SyncLock syncObject
If stopFlag Then
Return
End If
If pauseFlag Then
Console.WriteLine(C)
Monitor.Wait(syncObject)
Continue Do
End If
End SyncLock
C += 1
If C = Decimal.MaxValue Then C = 0
Loop
Console.WriteLine("Thread terminato")
End Sub
Abbiamo aggiunto un blocco SyncLock su syncObject in modo da poter
usare le variabili stopFlag e pauseFlag in maniera sicura. Se si
verifica che stopFlag è vero semplicemente usciamo dal ciclo e
interrompiamo il thread, se invece viene richiesto di entrare in uno
stato di pausa, mostriamo a schermo il numero a cui siamo arrivati a
contare e poi effettuiamo una chiamata a Monitor.Wait su
syncObject (su
cui abbiamo un lock): così facendo il thread si interromperà fino a
quando qualcuno non effettuerà una Pulse su syncObject.
Do While command <> "quit"
Select Case command
Case "pause"
SyncLock syncObject
pauseFlag = True
End SyncLock
Case "resume"
SyncLock syncObject
pauseFlag = False
Monitor.Pulse(syncObject)
End SyncLock
Case Else
Console.WriteLine("Unknown command")
End Select
command = Console.ReadLine()
Loop
SyncLock syncObject
stopFlag = True
If pauseFlag Then
pauseFlag = False
Monitor.Pulse(syncObject)
End If
End SyncLock
Nel caso l'utente richieda di interrompere temporaneamente il
conteggio, semplicemente acquisiamo un lock su syncObject e impostiamo
il pauseFlag su vero. Quando poi viene richiesto di riattivare il thread
acquisiamo il lock, impostiamo pauseFlag su falso ed effettuiamo il
Pulse su syncObject (su cui anche in questo caso abbiamo un lock): in
questo modo il thread che si trovava nello stato di wait riprenderà
l'esecuzione da dove era stata interrotta (Monitor.Wait) e il conteggio
proseguirà. INTERRUZIONE DI PIÙ THREAD Proviamo ora a vedere come potrebbe funzionare la sincronizzazione in presenza di molti thread che lavorano contemporaneamente. Il codice del thread rimane esattamente lo stesso, semplicemente ne creiamo molti:
Dim ThreadCount As Integer
Console.Write("Number of threads: ")
ThreadCount = CInt(Console.ReadLine())
Dim counterThreads(ThreadCount - 1) As Thread
For C1 As Integer = 0 To counterThreads.Length - 1
counterThreads(C1) = New Thread(AddressOf Counter)
counterThreads(C1).Start()
Next C1
In questa maniera quando verrà dato il comando di pause, ci ritroveremo ad avere diversi thread in attesa e una chiamata a Pulse ne sbloccherebbe solamente uno, per questo motivo esiste il metodo PulseAll che effettua un Pulse su tutti i thread attualmente in attesa sbloccandoli tutti:
Select Case command
Case "pause"
SyncLock syncObject
pauseFlag = True
End SyncLock
Case "resume one"
SyncLock syncObject
pauseFlag = False
Monitor.Pulse(syncObject)
End SyncLock
Case "resume all", "resume"
SyncLock syncObject
pauseFlag = False
Monitor.PulseAll(syncObject)
End SyncLock
Case Else
Console.WriteLine("Unknown command")
End Select
|
|||
| << INDIETRO | by VeNoM00 | ||