Condividi tramite


Procedura dettagliata: modifica di componenti multithreading semplici con Visual Basic

Benché il componente BackgroundWorker sostituisca lo spazio dei nomi System.Threading aggiungendovi funzionalità, lo spazio dei nomi System.Threading viene mantenuto per compatibilità con le versioni precedenti e per un eventuale utilizzo futuro. Per ulteriori informazioni, vedere Cenni preliminari sul componente BackgroundWorker.

È possibile scrivere applicazioni che consentono di eseguire più attività contemporaneamente. Questa caratteristica, detta multithreading o free threading, è il sistema ideale per progettare componenti che utilizzano il processore in modo intensivo e richiedono l'input dell'utente. Il multithreading può ad esempio essere sfruttato da un componente che calcola i dati relativi ai fogli paga. Il componente è in grado di elaborare i dati immessi in un database da un utente su un thread mentre i calcoli degli stipendi, che richiedono un utilizzo intensivo del processore, vengono eseguiti su un altro thread. Eseguendo i processi su thread separati, gli utenti non dovranno attendere che il computer completi i calcoli per immettere nuovi dati. In questa procedura viene creato un componente multithreading semplice in grado di effettuare contemporaneamente diversi calcoli complessi.

Creazione del progetto

L'applicazione è costituita da un unico form e un componente. L'utente immetterà i valori, segnalando al componente di avviare i calcoli. Il form riceverà i valori dal componente e li visualizzerà nei controlli label. Il componente eseguirà quindi i calcoli, che richiedono un utilizzo intensivo del processore, e ne segnalerà il completamento al form. Sarà necessario creare delle variabili pubbliche nel componente per contenere i valori ricevuti dall'interfaccia utente e si dovranno inoltre implementare i metodi per l'esecuzione dei calcoli sulla base dei valori di tali variabili.

Nota

Sebbene in genere sia preferibile utilizzare una funzione per un metodo che calcola un valore, gli argomenti non possono essere passati da un thread all'altro e non è possibile ottenere la restituzione di valori. Esistono molti sistemi semplici per fornire valori ai thread e ricevere valori da questi ultimi. In questa dimostrazione, la restituzione dei valori all'interfaccia utente verrà ottenuta mediante l'aggiornamento delle variabili pubbliche e si utilizzeranno eventi per notificare al programma principale il completamento dell'esecuzione da parte di un thread.

È possibile che le finestre di dialogo e i comandi di menu visualizzati siano diversi da quelli descritti nella Guida a seconda delle impostazioni attive o dell'edizione del programma. Per modificare le impostazioni, scegliere Importa/Esporta impostazioni dal menu Strumenti. Per ulteriori informazioni, vedere Gestione delle impostazioni.

Per creare il form

  1. Creare un nuovo progetto Applicazione Windows.

  2. Denominare l'applicazione Calculations e modificare il nome di Form1.vb in frmCalculations.vb

  3. Quando in Visual Studio viene richiesto di rinominare l'elemento di codice Form1, scegliere .

    Il form fungerà da interfaccia utente primaria per l'applicazione.

  4. Aggiungere al form cinque controlli Label, quattro controlli Button e un controllo TextBox.

    Controllo

    Nome

    Testo

    Label1

    lblFactorial1

    (vuoto)

    Label2

    lblFactorial2

    (vuoto)

    Label3

    lblAddTwo

    (vuoto)

    Label4

    lblRunLoops

    (vuoto)

    Label5

    lblTotalCalculations

    (vuoto)

    Button1

    btnFactorial1

    Factorial

    Button2

    btnFactorial2

    Factorial - 1

    Button3

    btnAddTwo

    Add Two

    Button4

    btnRunLoops

    Run a Loop

    TextBox1

    txtValue

    (vuoto)

Per creare il componente Calculator

  1. Scegliere Aggiungi componente dal menu Progetto.

  2. Denominare il componente Calculator.

Per aggiungere variabili pubbliche al componente Calculator

  1. Aprire l'editor di codice per Calculator.

  2. Aggiungere le istruzioni necessarie per la creazione delle variabili pubbliche che verranno utilizzate per passare i valori da frmCalculations a ogni thread.

    La variabile varTotalCalculations manterrà il conteggio dei calcoli eseguiti dal componente, mentre le altre variabili riceveranno valori dal form.

    Public varAddTwo As Integer
    Public varFact1 As Integer
    Public varFact2 As Integer
    Public varLoopValue As Integer
    Public varTotalCalculations As Double = 0
    

Per aggiungere metodi ed eventi al componente Calculator

  1. Dichiarare gli eventi che il componente utilizzerà per comunicare i valori al form. Immediatamente sotto le dichiarazioni delle variabili immesse nel passaggio precedente, digitare il seguente codice:

    Public Event FactorialComplete(ByVal Factorial As Double, ByVal _
       TotalCalculations As Double)
    Public Event FactorialMinusComplete(ByVal Factorial As Double, ByVal _
       TotalCalculations As Double)
    Public Event AddTwoComplete(ByVal Result As Integer, ByVal _
       TotalCalculations As Double)
    Public Event LoopComplete(ByVal TotalCalculations As Double, ByVal _
       Counter As Integer)
    
  2. Immediatamente sotto le dichiarazioni delle variabili immesse nel passaggio 1, digitare il seguente codice:

    ' This sub will calculate the value of a number minus 1 factorial 
    ' (varFact2-1!).
    Public Sub FactorialMinusOne()
       Dim varX As Integer = 1
       Dim varTotalAsOfNow As Double
       Dim varResult As Double = 1
       ' Performs a factorial calculation on varFact2 - 1.
       For varX = 1 to varFact2 - 1
          varResult *= varX
          ' Increments varTotalCalculations and keeps track of the current
          ' total as of this instant.
          varTotalCalculations += 1
          varTotalAsOfNow = varTotalCalculations
       Next varX
       ' Signals that the method has completed, and communicates the 
       ' result and a value of total calculations performed up to this 
       ' point
       RaiseEvent FactorialMinusComplete(varResult, varTotalAsOfNow)
    End Sub
    
    ' This sub will calculate the value of a number factorial (varFact1!).
    Public Sub Factorial()
       Dim varX As Integer = 1
       Dim varResult As Double = 1
       Dim varTotalAsOfNow As Double = 0
       For varX = 1 to varFact1
           varResult *= varX
           varTotalCalculations += 1
           varTotalAsOfNow = varTotalCalculations
       Next varX
       RaiseEvent FactorialComplete(varResult, varTotalAsOfNow)
    End Sub
    
    ' This sub will add two to a number (varAddTwo + 2).
    Public Sub AddTwo()
       Dim varResult As Integer
       Dim varTotalAsOfNow As Double
       varResult = varAddTwo + 2
       varTotalCalculations += 1
       varTotalAsOfNow = varTotalCalculations
       RaiseEvent AddTwoComplete(varResult, varTotalAsOfNow)
    End Sub
    
    ' This method will run a loop with a nested loop varLoopValue times.
    Public Sub RunALoop()
       Dim varX As Integer
       Dim varY As Integer
       Dim varTotalAsOfNow As Double
       For varX = 1 To varLoopValue
          ' This nested loop is added solely for the purpose of slowing
          ' down the program and creating a processor-intensive
          ' application.
          For varY = 1 To 500
             varTotalCalculations += 1
             varTotalAsOfNow = varTotalCalculations
          Next
       Next
       RaiseEvent LoopComplete(varTotalAsOfNow, varX - 1)
    End Sub
    

Trasferimento dell'input dell'utente al componente

Il passaggio successivo prevede l'aggiunta a frmCalculations del codice che consente di ricevere l'input dell'utente e di trasferire e ricevere valori da e verso il componente Calculator.

Per implementare la funzionalità front-end in frmCalculations

  1. Scegliere Compila soluzione dal menu Compila.

  2. Aprire frmCalculations in Progettazione Windows Form.

  3. Accedere alla scheda Componenti Calculations della Casella degli strumenti. Trascinare un componente Calculator sull'area di progettazione.

  4. Fare clic sul pulsante Eventi nella finestra Proprietà.

  5. Fare doppio clic su ognuno dei quattro eventi per creare i gestori eventi in frmCalculations. Sarà necessario tornare alla finestra di progettazione dopo aver creato ogni gestore eventi.

  6. Inserire il codice seguente per la gestione degli eventi che il form riceverà da Calculator1:

    Private Sub Calculator1_AddTwoComplete(ByVal Result As System.Int32, ByVal TotalCalculations As System.Double) Handles Calculator1.AddTwoComplete
        lblAddTwo.Text = Result.ToString
        btnAddTwo.Enabled = True
        lblTotalCalculations.Text = "TotalCalculations are " & _
            TotalCalculations.ToString
    End Sub
    
    Private Sub Calculator1_FactorialComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialComplete
        ' Displays the returned value in the appropriate label.
        lblFactorial1.Text = Factorial.ToString
        ' Re-enables the button so it can be used again.
        btnFactorial1.Enabled = True
        ' Updates the label that displays the total calculations performed
        lblTotalCalculations.Text = "TotalCalculations are " & _
           TotalCalculations.ToString
    End Sub
    
    Private Sub Calculator1_FactorialMinusComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialMinusComplete
        lblFactorial2.Text = Factorial.ToString
        btnFactorial2.Enabled = True
        lblTotalCalculations.Text = "TotalCalculations are " & _
            TotalCalculations.ToString
    End Sub
    
    Private Sub Calculator1_LoopComplete(ByVal TotalCalculations As System.Double, ByVal Counter As System.Int32) Handles Calculator1.LoopComplete
        btnRunLoops.Enabled = True
        lblRunLoops.Text = Counter.ToString
        lblTotalCalculations.Text = "TotalCalculations are " & _
           TotalCalculations.ToString
    End Sub
    
  7. Individuare l'istruzione End Class nella parte inferiore dell'editor di codice. Immediatamente prima di questa istruzione, aggiungere il codice seguente per gestire i clic dei pulsanti:

    Private Sub btnFactorial1_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnFactorial1.Click
       ' Passes the value typed in the txtValue to Calculator.varFact1.
       Calculator1.varFact1 = CInt(txtValue.Text)
       ' Disables the btnFactorial1 until this calculation is complete.
       btnFactorial1.Enabled = False
       Calculator1.Factorial()
    End Sub
    
    Private Sub btnFactorial2_Click(ByVal sender As Object, ByVal e _
       As System.EventArgs) Handles btnFactorial2.Click
       Calculator1.varFact2 = CInt(txtValue.Text)
       btnFactorial2.Enabled = False
       Calculator1.FactorialMinusOne()
    End Sub
    
    Private Sub btnAddTwo_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnAddTwo.Click
       Calculator1.varAddTwo = CInt(txtValue.Text)
       btnAddTwo.Enabled = False
       Calculator1.AddTwo()
    End Sub
    
    Private Sub btnRunLoops_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnRunLoops.Click
       Calculator1.varLoopValue = CInt(txtValue.Text)
       btnRunLoops.Enabled = False
       ' Lets the user know that a loop is running.
       lblRunLoops.Text = "Looping"
       Calculator1.RunALoop()
    End Sub
    

Test dell'applicazione

A questo punto è stato creato un progetto che incorpora un form e un componente in grado di eseguire diversi calcoli complessi. Sebbene non sia stata ancora implementata la funzionalità multithreading, prima di procedere sarà necessario eseguire il test del progetto per verificarne le funzionalità.

Per eseguire il test del progetto

  1. Scegliere Avvia debug dal menu Debug. L'applicazione viene avviata e viene visualizzato frmCalculations.

  2. Nella casella di testo digitare 4 quindi fare clic sul pulsante Add Two.

    Nell'etichetta sottostante dovrebbe apparire il numero "6", mentre in lblTotalCalculations dovrebbe essere visualizzata l'indicazione "Total Calculations are 1".

  3. Fare clic sul pulsante con etichetta Factorial - 1.

    Nell'etichetta sotto il pulsante verrà visualizzato il numerale "6", mentre in lblTotalCalculations verrà visualizzato il messaggio "Total Calculations are 4".

  4. Digitare 20 per modificare il valore della casella di testo, quindi fare clic sul pulsante Factorial.

    Sotto il pulsante verrà visualizzato il numero "2.43290200817664E+18" e in lblTotalCalculations verrà visualizzato il messaggio "Total Calculations are 24".

  5. Digitare 50000 per modificare il valore della casella di testo, quindi fare clic sul pulsante Run A Loop.

    Si noti che sarà necessario attendere brevemente che il pulsante venga nuovamente abilitato. L'etichetta sotto il pulsante indicherà "50000" e il totale dei calcoli visualizzato sarà "25000024".

  6. Digitare 5000000 per modificare il valore nella casella di testo, fare clic sul pulsante Run A Loop, quindi fare subito clic sul pulsante Add Two. Scegliere Add Two di nuovo.

    Il pulsante non risponderà, analogamente a qualsiasi altro controllo del form, finché i cicli non saranno completati.

    Se nel programma viene eseguito un singolo thread di esecuzione, i calcoli che richiedono un utilizzo intensivo del processore, riportati nell'esempio precedente, tenderanno a bloccare il programma fino a quando non verranno completati. Nella sezione successiva verrà aggiunta la funzionalità multithreading all'applicazione in modo da consentire l'esecuzione simultanea di più thread.

Aggiunta della funzionalità multithreading

Nell'esempio precedente sono state illustrate le limitazioni delle applicazioni con un singolo thread di esecuzione. Nella sezione successiva si utilizzerà la classe Thread per aggiungere più thread di esecuzione al componente.

Per aggiungere la subroutine Threads

  1. Aprire Calculator.vb nell'editor di codice. Nella parte superiore del codice individuare la riga Public Class Calculator . Immediatamente sotto di essa digitare il seguente codice:

    ' Declares the variables you will use to hold your thread objects.
    Public FactorialThread As System.Threading.Thread
    Public FactorialMinusOneThread As System.Threading.Thread
    Public AddTwoThread As System.Threading.Thread
    Public LoopThread As System.Threading.Thread
    
  2. Immediatamente prima dell'istruzione End Class nella parte inferiore del codice, aggiungere il seguente metodo:

    Public Sub ChooseThreads(ByVal threadNumber As Integer)
    ' Determines which thread to start based on the value it receives.
       Select Case threadNumber
          Case 1
             ' Sets the thread using the AddressOf the subroutine where
             ' the thread will start.
             FactorialThread = New System.Threading.Thread(AddressOf _
                Factorial)
             ' Starts the thread.
             FactorialThread.Start()
          Case 2
             FactorialMinusOneThread = New _
                System.Threading.Thread(AddressOf FactorialMinusOne)
             FactorialMinusOneThread.Start()
          Case 3
             AddTwoThread = New System.Threading.Thread(AddressOf AddTwo)
             AddTwoThread.Start()
          Case 4
             LoopThread = New System.Threading.Thread(AddressOf RunALoop)
             LoopThread.Start()
       End Select
    End Sub
    

    La creazione di un'istanza di un oggetto Thread richiede un argomento sotto forma di oggetto ThreadStart. L'oggetto ThreadStart è un delegato che fa riferimento all'indirizzo della subroutine in cui il thread verrà avviato. Un oggetto ThreadStart non può accettare parametri o passare valori e non può quindi indicare una funzione. L'oggetto AddressOf Operator restituisce un delegato che funge da oggetto ThreadStart. La subroutine ChooseThreads appena implementata riceverà un valore dal programma dal quale viene chiamata e utilizzerà tale valore per determinare il thread appropriato da avviare.

Per aggiungere il codice appropriato a frmCalculations

  1. Aprire frmCalculator.vb nell'editor di codice. Individuare Sub btnFactorial1_Click.

    1. Impostare come commento la riga di chiamata al metodo Calculator1.Factorial nel seguente modo:

      ' Calculator1.Factorial
      
    2. Per chiamare il metodo Calculator1.ChooseThreads aggiungere la seguente riga alla chiamata:

      ' Passes the value 1 to Calculator1, thus directing it to start the ' correct thread.
      Calculator1.ChooseThreads(1)
      
  2. Apportare modifiche analoghe alle altre subroutine button_click.

    Nota

    Accertarsi di includere il valore corretto per l'argomento threads.

    Al termine, il codice dovrebbe risultare simile al seguente:

    Private Sub btnFactorial1_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnFactorial1.Click
       ' Passes the value typed in the txtValue to Calculator.varFact1.
       Calculator1.varFact1 = CInt(txtValue.Text)
       ' Disables the btnFactorial1 until this calculation is complete.
       btnFactorial1.Enabled = False
       ' Calculator1.Factorial()
       ' Passes the value 1 to Calculator1, thus directing it to start the
       ' Correct thread.
       Calculator1.ChooseThreads(1)
    End Sub
    
    Private Sub btnFactorial2_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnFactorial2.Click
       Calculator1.varFact2 = CInt(txtValue.Text)
       btnFactorial2.Enabled = False
       ' Calculator1.FactorialMinusOne()
       Calculator1.ChooseThreads(2)
    End Sub
    
    Private Sub btnAddTwo_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnAddTwo.Click
       Calculator1.varAddTwo = CInt(txtValue.Text)
       btnAddTwo.Enabled = False
       ' Calculator1.AddTwo()
       Calculator1.ChooseThreads(3)
    End Sub
    
    Private Sub btnRunLoops_Click(ByVal sender As Object, ByVal e As _
       System.EventArgs) Handles btnRunLoops.Click
       Calculator1.varLoopValue = CInt(txtValue.Text)
       btnRunLoops.Enabled = False
       ' Lets the user know that a loop is running.
       lblRunLoops.Text = "Looping"
       ' Calculator1.RunALoop()
       Calculator1.ChooseThreads(4)
    End Sub
    

Chiamate di marshalling ai controlli

In questa sezione verrà illustrato come agevolare l'aggiornamento degli elementi visualizzati nel form. Poiché i controlli sono sempre di proprietà del thread di esecuzione principale, qualsiasi chiamata effettuata a un controllo da un thread subordinato richiede una chiamata di marshalling. Il marshalling implica lo spostamento di una chiamata da un limite all'altro del thread ed è un processo piuttosto dispendioso in termini di risorse. Per ridurre al minimo l'entità del marshalling e garantire che le chiamate vengano gestite in modo indipendente dal thread, si utilizzerà il metodo BeginInvoke per richiamare i metodi del thread di esecuzione principale. Questo tipo di chiamata è necessaria quando si chiamano metodi che modificano i controlli. Per ulteriori informazioni, vedere Procedura: modificare i controlli dai thread.

Per creare procedure di richiamo dei controlli

  1. Aprire l'editor di codice per frmCalculations. Nella sezione delle dichiarazioni aggiungere il seguente codice:

    Public Delegate Sub FHandler(ByVal Value As Double, ByVal _
       Calculations As Double)
    Public Delegate Sub A2Handler(ByVal Value As Integer, ByVal _
       Calculations As Double)
    Public Delegate Sub LDhandler(ByVal Calculations As Double, ByVal _
       Count As Integer)
    

    I metodi Invoke e BeginInvoke richiedono come argomento un delegato per il metodo appropriato. In queste righe vengono dichiarate le firme dei delegati che verranno utilizzate da BeginInvoke per richiamare i metodi appropriati.

  2. Aggiungere al codice i seguenti metodi vuoti:

    Public Sub FactHandler(ByVal Factorial As Double, ByVal TotalCalculations As _
       Double)
    End Sub
    Public Sub Fact1Handler(ByVal Factorial As Double, ByVal TotalCalculations As _
       Double)
    End Sub
    Public Sub Add2Handler(ByVal Result As Integer, ByVal TotalCalculations As _
       Double)
    End Sub
    Public Sub LDoneHandler(ByVal TotalCalculations As Double, ByVal Counter As _
       Integer)
    End Sub
    
  3. Utilizzare i comandi Taglia e Copia del menu Modifica per tagliare tutto il codice da Sub Calculator1_FactorialComplete e incollarlo in FactHandler.

  4. Ripetere il passaggio precedente per Calculator1_FactorialMinusComplete e Fact1Handler, Calculator1_AddTwoComplete e Add2Handler, e Calculator1_LoopComplete e LDoneHandler.

    Al termine, non vi sarà codice rimanente in Calculator1_FactorialComplete, Calculator1_FactorialMinusComplete, Calculator1_AddTwoComplete e Calculator1_LoopComplete e tutto il codice precedentemente contenuto in tali metodi sarà stato spostato nei nuovi metodi.

  5. Chiamare il metodo BeginInvoke per richiamare i metodi in modo asincrono. È possibile chiamare il metodo BeginInvoke dal form (me) o da qualsiasi controllo del form.

    Private Sub Calculator1_FactorialComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialComplete
       ' BeginInvoke causes asynchronous execution to begin at the address
       ' specified by the delegate. Simply put, it transfers execution of 
       ' this method back to the main thread. Any parameters required by 
       ' the method contained at the delegate are wrapped in an object and 
       ' passed. 
       Me.BeginInvoke(New FHandler(AddressOf FactHandler), New Object() _
          {Factorial, TotalCalculations })
    End Sub
    
    Private Sub Calculator1_FactorialMinusComplete(ByVal Factorial As System.Double, ByVal TotalCalculations As System.Double) Handles Calculator1.FactorialMinusComplete
       Me.BeginInvoke(New FHandler(AddressOf Fact1Handler), New Object() _
          { Factorial, TotalCalculations })
    End Sub
    
    Private Sub Calculator1_AddTwoComplete(ByVal Result As System.Int32, ByVal TotalCalculations As System.Double) Handles Calculator1.AddTwoComplete
       Me.BeginInvoke(New A2Handler(AddressOf Add2Handler), New Object() _
          { Result, TotalCalculations })
    End Sub
    
    Private Sub Calculator1_LoopComplete(ByVal TotalCalculations As System.Double, ByVal Counter As System.Int32) Handles Calculator1.LoopComplete
       Me.BeginInvoke(New LDHandler(AddressOf Ldonehandler), New Object() _
          { TotalCalculations, Counter })
    End Sub
    

    Apparentemente il gestore eventi effettua semplicemente una chiamata al metodo successivo, ma in realtà viene richiamato un metodo sul thread principale dell'operazione. Questo tipo di approccio consente di ridurre il numero di chiamate effettuate oltre i limiti del thread e di eseguire le applicazioni con multithreading in modo efficiente senza causare problemi di blocco del sistema. Per dettagli sull'utilizzo di un ambiente con multithreading, vedere Procedura: modificare i controlli dai thread.

  6. Salvare il lavoro.

  7. Eseguire il test della soluzione scegliendo Avvia debug dal menu Debug.

    1. Digitare 10000000 nella casella di testo e fare clic su Run A Loop.

      Nell'etichetta sotto il pulsante viene visualizzato "Looping". L'esecuzione di questo ciclo dovrebbe richiedere parecchio tempo. Se viene completato troppo presto, modificare il numero di conseguenza.

    2. Fare clic su tutti e tre i pulsanti ancora attivi in rapida successione. Tutti i pulsanti risponderanno all'input. Il primo risultato viene visualizzato nell'etichetta sotto il pulsante Add Two. I risultati successivi verranno visualizzati nelle etichette sotto i pulsanti Factorial. I risultati restituiti corrispondono a un numero infinito, in quanto il numero restituito da un Factorial pari a 10,000,000 è troppo grande per essere contenuto in una variabile in precisione doppia. Infine, dopo un ulteriore ritardo, i risultati vengono restituiti e visualizzati anche sotto il pulsante Run A Loop.

      Come si è potuto osservare, quattro diversi calcoli sono stati eseguiti contemporaneamente su quattro thread distinti, l'interfaccia utente ha continuato a rispondere all'input e i risultati sono stati restituiti al completamento di ciascun thread.

Coordinamento dei thread

L'utente esperto di applicazioni con multithreading potrebbe notare un lieve errore dovuto al codice così come è stato creato. Richiamare le righe di codice da ciascuna subroutine per l'elaborazione dei calcoli in Calculator:

varTotalCalculations += 1
varTotalAsOfNow = varTotalCalculations

Queste due righe di codice consentono di incrementare la variabile pubblica varTotalCalculations e di impostare la variabile locale varTotalAsOfNow su tale valore. Il valore viene quindi restituito a frmCalculations e visualizzato in un controllo label. Il problema è stabilire se viene restituito il valore corretto. La risposta è affermativa nel caso in cui venga utilizzato un solo thread di esecuzione. Se al contrario si utilizzano più thread, il valore restituito potrebbe non essere corretto. Ogni thread, infatti, incrementa la variabile varTotalCalculations ed è possibile che, se un thread ha incrementato la variabile ma non ne ha ancora copiato il valore in varTotalAsOfNow, un altro thread incrementi anch'esso il valore della variabile. È quindi possibile che ciascun thread restituisca risultati imprecisi. Per consentire la sincronizzazione dei thread e garantire che ciascun thread restituisca sempre un risultato preciso, utilizzare laIstruzione SyncLock. La sintassi per SyncLock è la seguente:

SyncLock AnObject
   Insert code that affects the object
   Insert some more
   Insert even more
' Release the lock
End SyncLock

Quando viene attivato il blocco SyncLock l'esecuzione dell'espressione specificata viene bloccata finché per il thread specificato è presente un blocco esclusivo dell'oggetto in questione. Nell'esempio riportato sopra, l'esecuzione viene bloccata su AnObject. È necessario utilizzare SyncLock con un oggetto che restituisca un riferimento anziché un valore. L'esecuzione può quindi precedere come blocco senza interferenze da parte di altri thread. Un insieme di istruzioni che vengono eseguite come unità costituisce un'operazione inscindibile. Quando si raggiunge il simbolo End SyncLock, l'espressione viene liberata e viene consentita la normale esecuzione dei thread.

Per aggiungere l'istruzione SyncLock all'applicazione

  1. Aprire Calculator.vb nell'editor di codice.

  2. Individuare tutte le istanze del seguente codice:

    varTotalCalculations += 1
    varTotalAsOfNow = varTotalCalculations
    

    Dovrebbero essere presenti quattro istanze, una per ciascun metodo di calcolo.

  3. Modificare il codice per ogni istanza nel seguente modo:

    SyncLock Me
       varTotalCalculations += 1
       varTotalAsOfNow = varTotalCalculations
    End SyncLock
    
  4. Salvare il lavoro ed eseguirne il test come nell'esempio precedente.

    Si noterà un lieve calo delle prestazioni del programma in quanto l'esecuzione dei thread viene interrotta quando si ottiene un blocco esclusivo del componente. Questo tipo di approccio, pur garantendo la precisione, annulla alcuni dei vantaggi offerti dal multithreading in termini di prestazioni. Si consiglia quindi di valutare attentamente la necessità di bloccare i thread e di implementare i blocchi solo quando è assolutamente necessario.

Vedere anche

Attività

Procedura: coordinare più thread di esecuzione

Procedura dettagliata: modifica di componenti multithreading semplici con Visual C#

Riferimenti

BackgroundWorker

Concetti

Cenni preliminari sul modello asincrono basato su eventi

Altre risorse

Programmazione con i componenti

Procedure dettagliate sulla programmazione dei componenti

Multithreading nei componenti