Exemplarische Vorgehensweise: Erstellen einer einfachen Multithreadkomponente mit Visual Basic

Aktualisiert: November 2007

Obwohl der System.Threading-Namespace durch die BackgroundWorker-Komponente ersetzt und funktionell erweitert wird, wird der System.Threading-Namespace sowohl aus Gründen der Abwärtskompatibilität als auch, falls gewünscht, für die zukünftige Verwendung beibehalten. Weitere Informationen finden Sie unter Übersicht über die BackgroundWorker-Komponente.

Sie können Anwendungen schreiben, die in der Lage sind, mehrere Aufgaben gleichzeitig auszuführen. Diese Fähigkeit, die Multithreading oder freies Threading genannt wird, ist eine leistungsstarke Methode, um rechenintensive Komponenten zu entwickeln, die Benutzereingaben benötigen. Ein Beispiel für eine Komponente, die sich Multithreading zunutze machen könnte, wäre eine Komponente, die Berechnungen in Lohnabrechnungen durchführt. Die Komponente könnte in einem Thread die vom Benutzer in eine Datenbank eingegebenen Daten verarbeiten, während ein anderer Thread die rechenintensiven Berechnungen für die Lohnabrechnung übernimmt. Da diese Prozesse in verschiedenen Threads ausgeführt werden, müssen Benutzer nicht warten, bis der Computer die Berechnungen beendet hat, um weitere Daten eingeben zu können. In dieser exemplarischen Vorgehensweise erstellen Sie eine einfache Multithreadkomponente, die verschiedene komplexe Berechnungen gleichzeitig durchführt.

Erstellen des Projekts

Die Beispielanwendung besteht aus einem einzelnen Formular und einer Komponente. Der Benutzer gibt Werte ein und signalisiert der Komponente, mit den Berechnungen zu beginnen. Das Formular empfängt dann Werte von der Komponente und zeigt sie in Label-Steuerelementen an. Die Komponente führt die rechenintensiven Berechnungen durch und signalisiert dem Formular das Ende der Berechnungen. Sie erstellen in der Komponente öffentliche Variablen für die Speicherung von Werten, die über die Benutzeroberfläche eingegeben wurden. Außerdem implementieren Sie Methoden, die basierend auf den Werten dieser Variablen die Berechnungen durchführen.

Hinweis:

Zwar wäre normalerweise für eine Methode, die einen Wert berechnet, eine Funktion vorzuziehen, allerdings können dann zwischen Threads weder Argumente übergeben noch Werte zurückgegeben werden. Es gibt eine Reihe von einfachen Möglichkeiten, Werte an Threads zu übergeben und Werte von ihnen zurückzuerhalten. In diesem Beispiel geben Sie Werte an die Benutzeroberfläche zurück, indem Sie Public-Variablen aktualisieren; das Hauptprogramm wird mithilfe von Ereignissen benachrichtigt, nachdem die Threadausführung beendet ist.

Je nach den aktiven Einstellungen oder der verwendeten Version können die angezeigten Dialogfelder und Menübefehle von den in der Hilfe beschriebenen abweichen. Klicken Sie im Menü Extras auf Einstellungen importieren und exportieren, um die Einstellungen zu ändern. Weitere Informationen finden Sie unter Visual Studio-Einstellungen.

So erstellen Sie das Formular

  1. Erstellen Sie ein neues Projekt vom Typ Windows-Anwendung.

  2. Nennen Sie die Anwendung Calculations, und benennen Sie Form1.vb in frmCalculations.vb um.

  3. Wenn Sie von Visual Studio aufgefordert werden, das Form1-Codeelement umzubenennen, klicken Sie auf Ja.

    Dieses Formular dient als primäre Benutzeroberfläche der Anwendung.

  4. Fügen Sie dem Formular fünf Label-Steuerelemente, vier Button-Steuerelemente und ein TextBox-Steuerelement hinzu.

    Steuerelement

    Name

    Text

    Label1

    lblFactorial1

    (leer)

    Label2

    lblFactorial2

    (leer)

    Label3

    lblAddTwo

    (leer)

    Label4

    lblRunLoops

    (leer)

    Label5

    lblTotalCalculations

    (leer)

    Button1

    btnFactorial1

    Factorial

    Button2

    btnFactorial2

    Factorial - 1

    Button3

    btnAddTwo

    Add Two

    Button4

    btnRunLoops

    Run a Loop

    TextBox1

    txtValue

    (leer)

So erstellen Sie die Calculator-Komponente

  1. Klicken Sie im Menü Projekt auf Komponente hinzufügen.

  2. Nennen Sie diese Komponente Calculator.

So fügen Sie der Calculator-Komponente öffentliche Variablen hinzu

  1. Öffnen Sie den Code-Editor für Calculator.

  2. Fügen Sie Anweisungen hinzu, um öffentliche Variablen zu erstellen, mit denen Sie Werte von frmCalculations an die jeweiligen Threads übergeben können.

    Die varTotalCalculations-Variable zählt fortlaufend die Gesamtzahl der von der Komponente durchgeführten Berechnungen, und die anderen Variablen erhalten ihre Werte vom Formular.

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

So fügen Sie der Calculator-Komponente Methoden und Ereignisse hinzu

  1. Deklarieren Sie die Ereignisse, die von der Komponente verwendet werden, um Werte an das Formular zu übermitteln. Fügen Sie unmittelbar unter die soeben eingegebenen Variablendeklarationen folgenden Code ein:

    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. Fügen Sie unmittelbar unter den in Schritt 1 eingegebenen Variablendeklarationen folgenden Code ein:

    ' 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
    

Übermitteln von Benutzereingaben an die Komponente

Im nächsten Schritt fügen Sie frmCalculations Code hinzu, um Benutzereingaben zu empfangen und Werte an die Calculator-Komponente zu übermitteln bzw. von der Komponente zu empfangen.

So implementieren Sie Front-End-Funktionalität für "frmCalculations"

  1. Klicken Sie im Menü Erstellen auf Projektmappe erstellen.

  2. Öffnen Sie frmCalculations im Windows Forms-Designer.

  3. Suchen Sie in der Toolbox die Registerkarte Berechnungskomponenten. Ziehen Sie eine Calculator-Komponente auf die Entwurfsoberfläche.

  4. Klicken Sie im Eigenschaftenfenster auf die Schaltfläche Ereignisse.

  5. Doppelklicken Sie auf jedes der vier Ereignisse, um Ereignishandler in frmCalculations zu erstellen. Nach der Erstellung der jeweiligen Ereignishandler müssen Sie zum Designer zurückkehren.

  6. Fügen Sie den folgenden Code ein, um die Ereignisse zu behandeln, die das Formular von Calculator1 empfängt:

    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. Suchen Sie die End Class-Anweisung im unteren Bereich des Code-Editors. Fügen Sie unmittelbar davor folgenden Code ein, der Klickereignisse auf Schaltflächen behandelt:

    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
    

Testen der Anwendung

Sie haben nun ein Projekt erstellt, das ein Formular und eine Komponente zur Durchführung mehrerer komplexer Berechnungen enthält. Obwohl Sie die Multithreadfunktionen noch nicht implementiert haben, sollten Sie die Funktionsfähigkeit des Projekts testen, bevor Sie fortfahren.

So testen Sie das Projekt

  1. Klicken Sie im Menü Debuggen auf Debuggen starten. Die Anwendung wird gestartet, und frmCalculations wird angezeigt.

  2. Geben Sie in das Textfeld 4 ein, und klicken Sie dann auf die Schaltfläche Add two.

    In der Bezeichnung darunter sollte nun die Zahl "6" angezeigt werden, und in lblTotalCalculations sollte "Total Calculations are 1" angezeigt werden.

  3. Klicken Sie nun auf die Schaltfläche Factorial - 1.

    Unter der Schaltfläche sollte das Nummernzeichen "6" angezeigt werden, und lblTotalCalculations sollte nun "Total Calculations are 4" lauten.

  4. Ändern Sie den Wert im Textfeld in 20, und klicken Sie dann auf die Schaltfläche Factorial.

    Darunter wird die Zahl "2,43290200817664E+18" angezeigt, und lblTotalCalculations lautet nun "Total Calculations are 24".

  5. Ändern Sie den Wert im Textfeld in 50000, und klicken Sie dann auf die Schaltfläche Run A Loop.

    Beachten Sie, dass Sie etwas warten müssen, bevor diese Schaltfläche wieder aktiviert wird. In der Beschriftung unter dieser Schaltfläche sollte "50000" angezeigt werden, und die Gesamtanzahl der Berechnungen beträgt "25000024".

  6. Ändern Sie den Wert im Textfeld in 5000000, und klicken Sie zunächst auf die Schaltfläche Run A Loop und unmittelbar danach auf die Schaltfläche Add Two. Klicken Sie erneut auf Add Two.

    Die Schaltfläche sowie alle anderen Steuerelemente im Formular reagieren erst wieder, nachdem die Schleifendurchläufe beendet wurden.

    Wenn ein Programm mit nur einem Thread arbeitet, wird das Programm durch rechenintensive Vorgänge wie im oben genannten Beispiel häufig so lange blockiert, bis sie vollständig ausgeführt sind. Im nächsten Abschnitt wird der Anwendung die Multithreadfunktionalität hinzugefügt, sodass mehrere Threads gleichzeitig ausgeführt werden können.

Hinzufügen der Multithreadfunktionalität

Im vorigen Beispiel wurden die Grenzen von Anwendungen aufgezeigt, die mit nur einem Thread ausgeführt werden. Im nächsten Abschnitt werden der Komponente mithilfe der Thread-Klasse mehrere Ausführungsthreads hinzugefügt.

So fügen Sie die Unterroutine "Threads" hinzu

  1. Öffnen Sie Calculator.vb im Code-Editor. Suchen Sie im oberen Bereich des Codes die Zeile Public Class Calculator . Fügen Sie direkt darunter folgenden Code ein:

    ' 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. Fügen Sie unmittelbar vor der End Class-Anweisung am Ende des Codes die folgende Methode hinzu:

    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
    

    Für die Instanziierung eines Thread-Objekts wird ein Argument in Form eines ThreadStart-Objekts benötigt. Das ThreadStart-Objekt ist ein Delegat, der auf die Adresse der Unterroutine verweist, bei der der Thread beginnen soll. Ein ThreadStart-Objekt kann keine Parameter enthalten oder Werte weitergeben; es kann daher keine Funktion angeben. Der AddressOf-Operator gibt einen Delegaten zurück, der als ThreadStart-Objekt fungiert. Die ChooseThreads-Unterroutine, die Sie eben implementiert haben, erhält einen Wert vom aufrufenden Programm und ermittelt aus diesem Wert den zu startenden Thread.

So fügen Sie "frmCalculations" Code zum Starten des Threads hinzu

  1. Öffnen Sie die Datei frmCalculations.vb im Code-Editor. Suchen Sie Sub btnFactorial1_Click.

    1. Kommentieren Sie die Zeile aus, durch die die Calculator1.Factorial-Methode direkt aufgerufen wird:

      ' Calculator1.Factorial
      
    2. Fügen Sie die folgende Zeile hinzu, um die Calculator1.ChooseThreads-Methode aufzurufen:

      ' Passes the value 1 to Calculator1, thus directing it to start the ' correct thread.
      Calculator1.ChooseThreads(1)
      
  2. Ändern Sie die anderen button_click-Unterroutinen in entsprechender Weise.

    Hinweis:

    Stellen Sie sicher, dass für das threads-Argument der richtige Wert angegeben wird.

    Anschließend sollte der Code ungefähr wie folgt aussehen:

    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
    

Marshallingaufrufe an Steuerelemente

Als Nächstes wird die Aktualisierung der Formularanzeige verbessert. Da Steuerelemente stets dem Hauptausführungsthread zugehörig sind, ist für jeden Aufruf eines Steuerelements von einem untergeordneten Thread ein Marshalling-Aufruf erforderlich. Als Marshallen bezeichnet man das Weiterleiten eines Aufrufs über Threadgrenzen hinweg. Dieser Vorgang ist sehr systemressourceintensiv. Um die Anzahl der erforderlichen Marshallingvorgänge zu minimieren und die threadsichere Behandlung der Aufrufe sicherzustellen, rufen Sie mit BeginInvoke Methoden im Hauptausführungsthread auf. Auf diese Weise wird die Anzahl der erforderlichen Marshallingvorgänge, die über Threadgrenzen hinausgehen, eingeschränkt. Diese Art von Aufruf ist notwendig, wenn Methoden aufgerufen werden sollen, die Auswirkungen auf Steuerelemente haben. Weitere Informationen finden Sie unter Gewusst wie: Bearbeiten von Steuerelementen aus Threads.

So erstellen Sie Prozeduren zum Aufrufen von Steuerelementen

  1. Öffnen Sie den Code-Editor für frmCalculations. Fügen Sie im Deklarationsabschnitt folgenden Code hinzu:

    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)
    

    Invoke und BeginInvoke benötigen als Argument einen Delegaten für die entsprechende Methode. Diese Zeilen deklarieren die Delegatsignaturen, die von BeginInvoke verwendet werden, um die entsprechenden Methoden aufzurufen.

  2. Fügen Sie dem Code die folgenden leeren Methoden hinzu.

    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. Klicken Sie im Menü Bearbeiten auf Ausschneiden und Einfügen, um den gesamten Code aus der Sub Calculator1_FactorialComplete auszuschneiden und in FactHandler einzufügen.

  4. Wiederholen Sie den vorherigen Schritt für Calculator1_FactorialMinusComplete und Fact1Handler, Calculator1_AddTwoComplete und Add2Handler sowie Calculator1_LoopComplete und LDoneHandler.

    Danach sollten Calculator1_FactorialComplete, Calculator1_FactorialMinusComplete, Calculator1_AddTwoComplete und Calculator1_LoopComplete keinen Code mehr enthalten, und der gesamte Code sollte nun in die entsprechenden neuen Methoden verschoben worden sein.

  5. Rufen Sie die BeginInvoke-Methode auf, um die Methoden asynchron aufzurufen. Sie können BeginInvoke entweder vom Formular (me) oder von jedem beliebigen Steuerelement im Formular aufrufen.

    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
    

    Auf den ersten Blick sieht es so aus, als ob der Ereignishandler einfach die nächste Methode aufruft. Tatsächlich bewirkt der Ereignishandler jedoch, dass eine Methode im Hauptausführungsthread aufgerufen wird. Diese Vorgehensweise vermeidet Aufrufe über Threadgrenzen hinweg. Die Multithreadanwendung arbeitet daher effizient und ohne zu blockieren. Ausführliche Informationen zum Arbeiten mit Steuerelementen in einer Multithreadumgebung finden Sie unter Gewusst wie: Bearbeiten von Steuerelementen aus Threads.

  6. Speichern Sie Ihre Arbeit.

  7. Testen Sie die Projektmappe, indem Sie im Menü Debuggen die Option Debuggen starten auswählen.

    1. Geben Sie in das Textfeld 10000000 ein, und klicken Sie auf Run A Loop.

      Unter dieser Schaltfläche wird die Bezeichnung "Looping" angezeigt. Die Ausführung dieser Schleife sollte einige Zeit in Anspruch nehmen. Geben Sie eine höhere Zahl ein, falls die Schleife zu schnell beendet wird.

    2. Klicken Sie in schneller Folge auf alle drei Schaltflächen, die noch aktiviert sind. Sie werden feststellen, dass alle Schaltflächen auf Ihre Eingabe reagieren. Die Bezeichnung unter Add Two sollte als Erste ein Ergebnis anzeigen. In den Bezeichnungen unter den Factorial-Schaltflächen werden die Ergebnisse etwas später angezeigt. Diese Ergebnisse gehen gegen Unendlich, da das Ergebnis der Fakultät von 10.000.000 für eine Variable des Typs double zu groß ist. Nach einer weiteren Verzögerung werden schließlich die Ergebnisse unter der Schaltfläche Run A Loop zurückgegeben.

      Wie Sie feststellen konnten, wurden in vier getrennten Threads gleichzeitig vier verschiedene Berechnungen ausgeführt. Die Benutzeroberfläche reagierte weiterhin auf Eingaben, und die Ergebnisse wurden zurückgegeben, nachdem der jeweilige Thread die Berechnung abgeschlossen hatte.

Koordinieren von Threads

Erfahrenen Benutzern von Multithreadanwendungen wird im zuvor eingegebenen Code eine kleine Ungenauigkeit aufgefallen sein. Alle Berechnungsunterroutinen in Calculator weisen die folgenden Codezeilen auf:

varTotalCalculations += 1
varTotalAsOfNow = varTotalCalculations

Diese beiden Zeilen erhöhen den Wert der öffentlichen Variablen varTotalCalculations und legen die lokale Variable varTotalAsOfNow auf den gleichen Wert fest. Dieser Wert wird dann an frmCalculations zurückgegeben und in einem Label-Steuerelement angezeigt. Aber wird auch der richtige Wert zurückgegeben? Wenn nur ein einziger Ausführungsthread verwendet wird, kann dies ganz klar bejaht werden. Im Falle mehrerer Threads ist die Antwort allerdings weniger eindeutig. Jeder Thread hat die Fähigkeit, den Wert der Variablen varTotalCalculations zu erhöhen. Es kann vorkommen, dass einer der Threads die Variable erhöht und gleich danach ein anderer Thread die Variable verändert, noch bevor der erste Thread den Wert in die Variable varTotalAsOfNow übertragen kann. Dadurch besteht die Gefahr, dass die einzelnen Threads tatsächlich falsche Ergebnisse liefern. Visual Basic stellt die SyncLock-Anweisung bereit, die die Synchronisierung der Threads ermöglicht und damit sicherstellt, dass jeder Thread stets ein richtiges Ergebnis zurückgibt. Die Syntax für SyncLock lautet wie folgt:

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

Wenn der SyncLock-Block eingegeben wird, wird die Ausführung in dem angegebenen Ausdruck blockiert, bis für das fragliche Objekt eine exklusive Sperre für den angegebenen Thread vorliegt. Im obigen Beispiel ist die Ausführung für AnObject blockiert. SyncLock muss mit Objekten verwendet werden, die statt eines Werts einen Verweis zurückgeben. Die Ausführung wird dann als Block fortgesetzt, ohne dass andere Threads Einfluss auf das Objekt nehmen können. Eine Reihe von Anweisungen, die als Einheit ausgeführt werden, wird als atomar bezeichnet. Nach Erreichen von End SyncLock wird der Ausdruck freigegeben, und die Threads können ganz normal fortgesetzt werden.

So fügen Sie der Anwendung die SyncLock-Anweisung hinzu

  1. Öffnen Sie Calculator.vb im Code-Editor.

  2. Suchen Sie alle Instanzen des folgenden Codes:

    varTotalCalculations += 1
    varTotalAsOfNow = varTotalCalculations
    

    Es müsste vier Instanzen dieses Codes geben, eine pro Berechnungsmethode.

  3. Ändern Sie diesen Code wie folgt:

    SyncLock Me
       varTotalCalculations += 1
       varTotalAsOfNow = varTotalCalculations
    End SyncLock
    
  4. Speichern Sie Ihre Arbeit, und testen Sie das Projekt wie im vorangegangenen Beispiel.

    Die Verarbeitungsgeschwindigkeit des Programms könnte etwas langsamer sein. Das liegt daran, dass die Ausführung der Threads angehalten wird, sobald ein Thread exklusiven Zugriff auf die Komponente erlangt hat. Obwohl dieser Ansatz einerseits Genauigkeit gewährleistet, kann er die Leistungsvorteile, die durch die Verwendung mehrerer Threads erzielt werden, andererseits mindern. Sie sollten sorgfältig abwägen, ob die Synchronisierung von Threads notwendig ist, und sie nur implementieren, wenn es zwingende Gründe dafür gibt.

Siehe auch

Aufgaben

Gewusst wie: Koordinieren mehrerer Ausführungsthreads

Exemplarische Vorgehensweise: Erstellen einer einfachen Multithreadkomponente mit Visual C#

Konzepte

Übersicht über ereignisbasierte asynchrone Muster

Referenz

BackgroundWorker

Weitere Ressourcen

Programmieren mit Komponenten

Exemplarische Vorgehensweisen für das Programmieren von Komponenten

Multithreading in Komponenten