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
Erstellen Sie ein neues Projekt vom Typ Windows-Anwendung.
Nennen Sie die Anwendung Calculations, und benennen Sie Form1.vb in frmCalculations.vb um.
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.
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
Klicken Sie im Menü Projekt auf Komponente hinzufügen.
Nennen Sie diese Komponente Calculator.
So fügen Sie der Calculator-Komponente öffentliche Variablen hinzu
Öffnen Sie den Code-Editor für Calculator.
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
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)
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"
Klicken Sie im Menü Erstellen auf Projektmappe erstellen.
Öffnen Sie frmCalculations im Windows Forms-Designer.
Suchen Sie in der Toolbox die Registerkarte Berechnungskomponenten. Ziehen Sie eine Calculator-Komponente auf die Entwurfsoberfläche.
Klicken Sie im Eigenschaftenfenster auf die Schaltfläche Ereignisse.
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.
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
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
Klicken Sie im Menü Debuggen auf Debuggen starten. Die Anwendung wird gestartet, und frmCalculations wird angezeigt.
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.
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.
Ä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".
Ä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".
Ä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
Ö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
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
Öffnen Sie die Datei frmCalculations.vb im Code-Editor. Suchen Sie Sub btnFactorial1_Click.
Kommentieren Sie die Zeile aus, durch die die Calculator1.Factorial-Methode direkt aufgerufen wird:
' Calculator1.Factorial
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)
Ä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
Ö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.
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
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.
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.
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.
Speichern Sie Ihre Arbeit.
Testen Sie die Projektmappe, indem Sie im Menü Debuggen die Option Debuggen starten auswählen.
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.
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
Öffnen Sie Calculator.vb im Code-Editor.
Suchen Sie alle Instanzen des folgenden Codes:
varTotalCalculations += 1 varTotalAsOfNow = varTotalCalculations
Es müsste vier Instanzen dieses Codes geben, eine pro Berechnungsmethode.
Ändern Sie diesen Code wie folgt:
SyncLock Me varTotalCalculations += 1 varTotalAsOfNow = varTotalCalculations End SyncLock
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
Weitere Ressourcen
Exemplarische Vorgehensweisen für das Programmieren von Komponenten