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