Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einführung
2 Grundlagen der Sprachsyntax
3 Klassendesign
4 Weitere Datentypen
5 Multithreading
6 Collections und LINQ
7 Eingabe und Ausgabe
8 Anwendungen: Struktur und Installation
9 Code erstellen und debuggen
10 Einige Basisklassen
11 Windows-Anwendungen erstellen
12 Die wichtigsten Steuerelemente
13 Tastatur- und Mausereignisse
14 MDI-Anwendungen
15 Grafiken mit GDI+
16 Drucken
17 Entwickeln von Steuerelementen
18 Programmiertechniken
19 WPF – Grundlagen
20 Layoutcontainer
21 WPF-Steuerelemente
22 Konzepte von WPF
23 Datenbankverbindung mit ADO.NET
24 Datenbankabfragen mit ADO.NET
25 DataAdapter
26 Offline mit DataSet
27 Datenbanken aktualisieren
28 Stark typisierte DataSets
A Anhang: Einige Übersichten
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual Basic 2008 von Andreas Kuehnel, Stephan Leibbrandt
Das umfassende Handbuch
Buch: Visual Basic 2008

Visual Basic 2008
3., aktualisierte und erweiterte Auflage, geb., mit DVD
1.323 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1171-0
Pfeil 5 Multithreading
Pfeil 5.1 Start eines Threads
Pfeil 5.1.1 Parameterloser Start
Pfeil 5.1.2 Start mit Parameter
Pfeil 5.1.3 Hintergrundthreads
Pfeil 5.1.4 Threadeigenschaften
Pfeil 5.2 Zusammenspiel
Pfeil 5.2.1 Priorität
Pfeil 5.2.2 Warten mit Sleep
Pfeil 5.2.3 Unterbrechen mit Interrupt
Pfeil 5.2.4 Abbruch mit Abort
Pfeil 5.2.5 Warten mit Join
Pfeil 5.2.6 Warten erzwingen mit Suspend
Pfeil 5.2.7 Threadzustände
Pfeil 5.2.8 Die Klasse Thread
Pfeil 5.3 Gesicherter Datenaustausch
Pfeil 5.3.1 Objekte sperren mit Monitor
Pfeil 5.3.2 Codebereiche mit SyncLock sperren
Pfeil 5.3.3 Sperrung mit Zustand
Pfeil 5.3.4 Atomare Operationen
Pfeil 5.3.5 Attributgesteuerte Synchronisation
Pfeil 5.4 Asynchrone Methodenaufrufe
Pfeil 5.4.1 Aufruf
Pfeil 5.4.2 Rückruf
Pfeil 5.4.3 Zustand
Pfeil 5.4.4 Rückgabe
Pfeil 5.4.5 ByRef-Parameter
Pfeil 5.5 Threadpools


Rheinwerk Computing - Zum Seitenanfang

5.2 Zusammenspiel Zur nächsten ÜberschriftZur vorigen Überschrift

Nachdem wir einige Threads zum Laufen gebracht haben, schauen wir uns an, was mit einem Thread noch passieren kann. Es gibt es drei wesentliche Zustände für einen aktiven Thread:

  • laufend (running): wird gerade im Prozessor ausgeführt
  • bereit (ready): in der Warteschlange des Prozessors
  • wartend (waiting): inaktiv, bis er von einem anderen Prozess geweckt wird

Nur während ein Thread im ersten Zustand ist, kann er Programmcode ausführen. Da er mit anderen Threads in Konkurrenz steht, dauert dieser Zustand nicht ewig. Für die Aufgabe dieses Zustands gibt es nur wenige Gründe:

  • Ein Thread höherer Priorität wird in der Warteschlange gestellt, woraufhin der Thread in die Warteschlange gestellt wird und der Thread mit höherer Priorität zur Ausführung kommt.
  • Die vom Betriebssystem zugewiesene Zeit ist abgelaufen, und der Thread wird mit dem Zustand bereit in die Warteschlange gestellt.
  • Der Thread gibt mit Sleep() sofort den Zustand freiwillig auf und geht nach einer Wartezeit in den Zustand bereit über.
  • Der Thread muss auf einen anderen warten, ruft die Methode Join() des anderen Threads auf und tritt in den Zustand wartend ein.

Ihr Programm ist nie das einzige, das ausgeführt wird (ebenso wie man nie der Erste auf der Autobahn ist). Daher ist die Zusammenarbeit mit anderen Threads selbst dann wichtig, wenn Ihr Programm nur einen davon verwendet. Die folgenden Abschnitte beschreiben die Aspekte der Zusammenarbeit von Threads.


Rheinwerk Computing - Zum Seitenanfang

5.2.1 Priorität Zur nächsten ÜberschriftZur vorigen Überschrift

Die Rangfolge der Aktivitäten auf dem Rechner ist von entscheidender Bedeutung. Liefen alle Programme völlig gleichberechtigt, würden Benutzereingaben zur Qual: Jede einzelne Berechnung würde die Maus derart ausbremsen, dass ein vernünftiges Arbeiten unmöglich wird. Daher bekommen einige Anwendungen, wie zum Beispiel die Steuerung der Maus und der Tastatur, eine höhere Priorität zugewiesen und werden damit vor allen anderen niederer Priorität ausgeführt. Auf der anderen Seite gibt es nachrangige Aufgaben, wie zum Beispiel die Aufräumarbeiten des Garbage-Collector-Threads, der sich um die Speicherbereinigung kümmert. Erst, wenn die Ressourcen knapp werden, erhält er eine höhere Priorität zugewiesen, damit er dann in jedem Fall zum Zuge kommen kann.

Sie können Threads in Ihren Programmen auch explizit eine Priorität zuweisen. Dazu weisen Sie der Eigenschaft Priority Ihres Threads einen der Werte der Enumeration ThreadPriority zu: Lowest, BelowNormal, Normal, AboveNormal, Highest. Auch wenn Windows 31 Stufen der Priorität kennt, beschränken sich .NET-Programme auf diese fünf Werte. Wenn Sie diese einfach für alle Ihre Programme auf einen hohen Wert setzen, bremsen Sie alle anderen Programme aus. Daher gilt für alle Threads:


Threads hoher Priorität dürfen nur das Nötigste in möglichst kurzer Zeit erledigen.


Auf der anderen Seite darf die Priorität auch nicht zu niedrig gewählt werden, denn:


Nur Threads im Zustand bereit mit der höchsten Priorität kommen zur Ausführung.


Das folgende Codefragment zeigt, wie ein Thread höherer Priorität einen mit normaler Priorität in der Ausführung behindert. Dazu startet der Hauptthread mit normaler Priorität eine Schleife. Nach einigen Durchläufen wird ein Thread mit höherer Priorität gestartet. Der Test, ob der Thread noch nicht gestartet wurde (Unstarted), verhindert einen erneuten Startversuch.


'...\ Multitasking\Zusammenspiel\Priorität.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Priorität 
    Sub Druck(ByVal z As Object) 
      For no As Integer = 0 To 1000 
        Console.Write(z) 
        If no = 5 AndAlso einheit.ThreadState = ThreadState.Unstarted Then _ 
          einheit.Start("P") 
      Next 
    End Sub

    Dim einheit As New Thread(AddressOf Druck)

    Sub Test() 
      einheit.Priority = ThreadPriority.AboveNormal 
      Druck("N") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt einige wenige Ausgaben »N« des Threads mit normaler Priorität, bevor dann alle Ausgaben »P« des Threads höherer Priorität erfolgen. Dass es mehr als fünf »N« sind, liegt an der Zeit, die zum Starten eines Threads benötigt wird. Erst nach dem Ende des höher priorisierten Threads kommt der Thread mit normaler Priorität wieder zum Zuge.

NNNNNNNNNPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 
... 
PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 
... 
NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN

Rheinwerk Computing - Zum Seitenanfang

5.2.2 Warten mit Sleep Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn eine Aktivität zu schnell ist, kann sie mit der Methode Sleep() ausgebremst werden. Dies kann zum Beispiel sinnvoll sein, um einen anderen Thread zum Zuge kommen zu lassen, damit Sie mit dem dann laufenden Thread kommunizieren können. Die Ruhezeit können Sie in Millisekunden oder als Zeitspanne angeben. Eine unendlich lange Wartezeit wird durch den speziellen Wert Timeout.Infinite repräsentiert (wie Sie da wieder herauskommen, zeigt der nächste Unterabschnitt).


Public Shared Sub Sleep(millisecondsTimeout As Integer) 
Public Shared Sub Sleep(timeout As TimeSpan)

Bitte beachten Sie, dass die Methode mit Shared an die Klasse Thread gebunden ist. Sie können daher nicht einen beliebigen Thread warten lassen, sondern nur der gerade laufende Thread kann sich selbst zur Ruhe begeben. Alles andere könnte den Programmablauf nachhaltig stören, wenn eine Aktivität zur Unzeit unterbrochen wird. So behält jeder Thread selbst die Kontrolle darüber, ob er eine Zeit aussetzt. Die Methode Sleep() arbeitet in zwei Schritten:

  • Freigabe des Prozessors
  • Einreihung in die Warteschlange nach der angegebenen Zeit

Das folgende Beispiel zeigt, dass es notwendig sein kann, einen Thread ruhen zu lassen. Beide Threads, der Hauptthread und ein explizit gestarteter, sollen abwechselnd Zahlen auf der Konsole mit der Methode Druck() ausgeben. Dazu werden beide Methoden hintereinander durch Start(2) und Druck(1) gestartet. Ohne den Aufruf von Sleep() in der Methode Druck() ist sehr wahrscheinlich die durch Start(2) erzeugte Ausgabe so schnell, dass Druck(1) erst zum Zuge kommt, wenn bereits alle Zweien ausgegeben worden sind. Der Aufruf Sleep(0) gibt den Prozessor frei und stellt den Thread gleich wieder in die Warteschlange, da die Wartezeit null ist.


'...\ Multitasking\Zusammenspiel\Schlafen.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Schlafen 
    Sub Druck(ByVal z As Object) 
      For no As Integer = 0 To 10 
        Thread.Sleep(0) 
        Console.Write(z) 
      Next 
    End Sub

    Sub Test() 
      Dim zweiter As New Thread(AddressOf Druck) 
      zweiter.Start(2) 
      Druck(1) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe der Zahlen erfolgt abwechselnd.

1212121212121212121212

Rheinwerk Computing - Zum Seitenanfang

5.2.3 Unterbrechen mit Interrupt Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Thread kann sich im Zustand wartend befinden, weil die Wartezeit eines Aufrufs von Sleep() noch nicht abgelaufen ist oder der Thread mit Join() den Prozessor für eine unbestimmte Zeit geräumt hat. Den zweiten Fall zeigt der übernächste Unterabschnitt. Durch welche der beiden Möglichkeiten der Thread in den Zustand wartend gekommen ist, spielt für die Beendigung des Zustandes keine Rolle. Der normale Programmablauf ist in diesem Zustand unmöglich. Der einzige Ausweg ist eine Ausnahme. Deshalb bietet die Klasse Thread die Methode Interrupt() an, um eine Ausnahme vom Typ ThreadInterruptedException auszulösen.


Public Sub Interrupt()

Wie bei jeder anderen Ausnahme auch, können Sie mit einem Try/Catch-Block die Ausnahme abfangen. Im nächsten Codefragment wird die Methode Arbeiter() in einem neuen Thread gestartet. Innerhalb der Methode legt sich der neue Thread mit Sleep() für »immer« schlafen. Die einzige Möglichkeit, Arbeiter() zu wecken, besteht in dem Aufruf von Interrupt() für den Thread arb von Arbeiter(). Dadurch wird die Ausnahme ausgelöst und im Catch-Zweig aufgefangen.


'...\ Multitasking\Zusammenspiel\Interrupt.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Unterbrechnung 
    Sub Arbeiter(ByVal o As Object) 
      Try 
        Thread.Sleep(Timeout.Infinite) 
        Console.WriteLine("Endlich ausgeschlafen.") 
      Catch ex As ThreadInterruptedException 
        Console.WriteLine("Siesta unterbrochen.") 
      End Try 
    End Sub

    Sub Test() 
      Dim arb As New Thread(AddressOf Arbeiter) 
      arb.Start() 
      arb.Interrupt() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Das Fehlen der Ausgabe "Endlich ausgeschlafen." zeigt, dass die Ausnahme, wie jede andere Ausnahme auch, den Code nach der Stelle der Ausnahmeauslösung, hier während Sleep(), überspringt.

Siesta unterbrochen.

Hinweis
Befindet sich ein Thread beim Aufruf von Interrupt() nicht im Zustand wartend, wird die Unterbrechung ausgeführt, wenn er das nächste Mal den Zustand erreicht.



Rheinwerk Computing - Zum Seitenanfang

5.2.4 Abbruch mit Abort Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn ein Thread für eine Aufgabe zu lange braucht, kann es sinnvoll sein, ihn von außen abzubrechen. Dazu bietet die Klasse Thread zwei Methoden an. Analog zu Interrupt() lösen sie eine Ausnahme vom Typ ThreadAbortException in dem Thread aus, mit dessen Referenz Abort() aufgerufen wurde. Anders als bei der Unterbrechung mit Interrupt() kann der Thread nicht nur im Zustand wartend sein, sondern auch im Zustand laufend oder bereit. Während Sie bei Interrupt() die Stelle der Ausnahmeauslösung eingrenzen konnten (durch Aufruf von Sleep() oder Join()), ist dies bei Abort() nicht möglich.


Public Sub Abort() 
Public Sub Abort(stateInfo As Object)

Sie sollten diese Möglichkeit der Terminierung nur im Notfall einsetzen, denn beim Aufruf wissen Sie nicht, was der abzubrechende Thread gerade macht. Schreibt er zum Beispiel gerade auf die Festplatte, können die geschriebenen Daten verloren sein. Wenn andererseits ein Thread auf ein nicht angeschlossenes Netzwerk zugreifen will, kann eine unbedingte Beendigung sinnvoll sein.


Hinweis
Wenn der Abbruch während einer Klasseninitialisierung passiert, kann es sein, dass andere Klassen nicht geladen werden können. Sie sollten diese Situation unbedingt vermeiden. Eine andere kritische Situation ergibt sich, wenn im Finally-Zweig der ThreadAbortException-Ausnahme sehr umfangreiche Anweisungen stehen. Dann bricht der Thread de facto nicht ab.


Das folgende Beispiel führt in einem eigenen Thread die Methode Rede()aus, die eine Ausgabe im Konsolenfenster erzeugt. Sie wird durch Sleep(1) ausgebremst, damit sie nicht fertig ist, bevor der Hauptthread einen Abbruch auslösen kann. Nach einer Pause von 200 Millisekunden wird der Thread mit Abort("Vorsitzender") zum Beenden aufgefordert. Dies löst in der Methode Rede() eine Ausnahme vom Typ ThreadAbortException aus, die in einem Catch-Zweig aufgefangen wird. Die dortige Ausgabe greift auf die Eigenschaft ExceptionState der Ausnahme zu, die den in Abort() übergebenen Parameter enthält. In einem Finally-Zweig wird noch eine Ausgabe erzeugt. Am Ende der Methode soll eine weitere Ausgabe erfolgen.


'...\ Multitasking\Zusammenspiel\Abbruch.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Abbruch 
    Sub Rede() 
      Try 
        For Each c As Char In "Um zum Ende meiner Rede zu kommen ..." 
          Console.Write(c) : Thread.Sleep(1) 
        Next 
      Catch ex As ThreadAbortException 
        Console.WriteLine("{0}Abgebrochen durch: {1}!", _ 
                          Environment.NewLine, ex.ExceptionState) 
      Finally 
        Console.WriteLine("(war wohl nix)") 
      End Try 
      Console.WriteLine("Ich lade Sie ein, mir zu folgen und ...") 
    End Sub

    Sub Test() 
      Dim politiker As New Thread(AddressOf Rede) 
      politiker.Start() 
      Thread.Sleep(200) 
      politiker.Abort("Vorsitzender") 
      Thread.Sleep(200) 
      Console.WriteLine("Redner redet weiter: {0}", politiker.IsAlive) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Zeile der Ausgabe zeigt, dass die Ausgabe abgebrochen wurde. Die Ausgaben in den Catch- und Finally-Zweigen zeigen die zweite und dritte Zeile. Die letzte Zeile bestätigt, dass der Thread tatsächlich beendet wurde.

Um zum Ende meine 
Abgebrochen durch: Vorsitzender! 
(war wohl nix) 
Redner redet weiter: False

Fehlt hier nicht die Ausgabe am Ende der Methode Rede()? Die Ausnahme haben wir ja abgefangen, und das Programm sollte nach dem Finally-Zweig fortgesetzt werden. Dies zeigt die besondere Stellung der Ausnahme ThreadAbortException. Ohne weitere Maßnahmen wird sie nämlich automatisch erneut ausgelöst. Wollen Sie dies verhindern und sich damit der drohenden Terminierung widersetzen, bietet die Klasse Thread die Methode ResetAbort() an.


Public Shared Sub ResetAbort()

Wie die Methode Sleep() ist auch diese Methode mit Shared an die Klasse gebunden und wirkt sich nur auf den gerade aktiven Thread aus. Dazu zeigt das folgende Beispiel einen solchen unterbundenen Abbruch. Der Unterschied zum vorigen Beispiel besteht in dem Aufruf ResetAbort() im Catch-Zweig, der eine erneute Auslösung der Ausnahme verhindert, und in dem unbedingten Sprung mit GoTo zum Methodenanfang, um die Methode nach einem Abbruchversuch noch einmal zu durchlaufen. Obwohl der Sprung unschön ist, lässt er sich hier kaum vermeiden. Zum Beispiel kann ein Try/Catch-Block in einer While-Schleife scheitern, wenn der Abbruch genau dann passiert, wenn die Bedingung im Schleifenkopf ausgeführt wird, da sich dieser außerhalb des Fehlerbehandlungsblocks befindet. Dies zeigt, dass das korrekte Abfangen einer Abbruchanforderung nicht trivial ist und im Programmdesign frühzeitig berücksichtigt werden sollte.


'...\ Multitasking\Zusammenspiel\Abbruchversuch.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Abbruchversuch 
    Sub Rede() 
neu:  Try 
        For Each c As Char In "Um zum Ende meiner Rede zu kommen ..." 
          Console.Write(c) : Thread.Sleep(1) 
        Next 
      Catch ex As ThreadAbortException 
        Console.WriteLine(Environment.NewLine & "(also nochmal)") 
        Thread.ResetAbort() 
        GoTo neu 
      End Try 
      Console.WriteLine(Environment.NewLine & "(fertig)") 
    End Sub

    Sub Test() 
      Dim vorsitzender As New Thread(AddressOf Rede) 
      vorsitzender.Start() 
      Thread.Sleep(200) 
      vorsitzender.Abort() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Im Gegensatz zum vorigen Beispiel wird die Methode nach der Abbruchanforderung neu gestartet und auch die Anweisungen nach dem Try/Catch-Block ausgeführt. Die Ausnahme ThreadAbortException ist also nicht erneut ausgelöst worden.

Um zum Ende m 
(also nochmal) 
Um zum Ende meiner Rede zu kommen ... 
(fertig)

Hinweis
Ist ein Thread beim Abbruch noch nicht gestartet, wird er beim Start abgebrochen. Ist er im Zustand wartend, wird er vor dem Abbruch ohne Auslösung der Ausnahme ThreadInterruptedException unterbrochen.



Rheinwerk Computing - Zum Seitenanfang

5.2.5 Warten mit Join Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn ein Thread sicherstellen muss, dass erst ein anderer Thread eine Aufgabe erledigt hat, bevor er selbst mit der Programmausführung fortfährt, kann er sich selbst in den Zustand wartend versetzen, indem er sich an den anderen Thread »dranhängt«. Dazu ruft er die Methode Join() des Threads auf, auf den er warten will. Der Rückgabewert ist True, wenn der andere Thread innerhalb der Zeitspanne fertig wurde.


Public Sub Join() 
Public Function Join(millisecondsTimeout As Integer) As Boolean 
Public Function Join(timeout As TimeSpan) As Boolean

Damit der wartende Thread weiterarbeiten kann, muss eine der folgenden Bedingungen erfüllt sein, die wir uns im Folgenden ansehen:

  • Der andere Thread ist beendet.
  • Die angegebene Zeitspanne ist abgelaufen.
  • Der andere Thread erweckt den wartenden Thread mit Interrupt() wieder zum Leben.

Hinweis
Warten zwei Threads jeweils aufeinander, können sie beide nicht weiterlaufen. Man nennt dies einen Deadlock. Nur ein dritter Thread kann das Patt auflösen. Gibt es ihn nicht, hat sich das Programm »aufgehängt«.


Das folgende Codefragment zeigt den einfachsten Fall, dass ein Thread einfach wartet, bis der andere beendet ist.


'...\ Multitasking\Zusammenspiel\Warten.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Warten 
    Sub Bad() 
      Thread.Sleep(1000) 
    End Sub

    Sub Test() 
      Dim morgens As New Thread(AddressOf Bad) 
      morgens.Start() 
      Console.Write("Warten von {0} ", Now) 
      morgens.Join() 
      Console.WriteLine("bis {0} (fertig: {1})", Now, Not morgens.IsAlive) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Zwischen den beiden Zeitangaben liegt (mindestens) die in Sleep() angegebene Zeit. Die Ausgabe zeigt auch, dass der andere Thread vor der Ausgabe beendet worden ist.

Warten von 9/18/2008 2:35:04 PM bis 9/18/2008 2:35:05 PM (fertig: True)

In vielen Situationen kann es sinnvoll sein, nur eine begrenzte Zeit auf den anderen Thread zu warten. Dazu wird im nächsten Beispiel, das den Ladevorgang mehrerer Dateien simuliert, eine maximale Wartezeit von 500 Millisekunden an die Methode Join() übergeben. Durch den Aufruf von Sleep() in der Methode Laden() ist der andere Thread nach dieser Zeit noch nicht fertig, und der Hauptthread fährt nach dem Aufruf von Join() fort. In dem Beispiel bricht Abort() den anderen Thread ab. Dieser gibt im Catch-Zweig den in Abort() angegebenen Parameter aus.


'...\ Multitasking\Zusammenspiel\Zeitüberschreitung.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Zeitüberschreitung 
    Sub Laden() 
      Try 
        Console.Write("Lade ") 
        For no As Integer = 0 To 10 
          Thread.Sleep(100) 
          Console.Write("Datei {0} ", no) 
        Next 
      Catch ex As ThreadAbortException 
        Console.WriteLine(ex.ExceptionState.ToString()) 
      End Try 
    End Sub

    Sub Test() 
      Dim arb As New Thread(AddressOf Laden) 
      arb.Start() 
      Console.WriteLine("Warten auf das Ende.") 
      Dim fertig As Boolean = arb.Join(500) 
      arb.Abort("Schluss jetzt.") 
      Console.WriteLine("Genug gewartet (fertig: {0}).", fertig) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Wie erwartet, werden weniger als 11 Dateien ausgegeben, und der Rückgabewert von Join() ist False. Ob die Ausgabe mitten im Wort abgebrochen wird und in welcher Reihenfolge die beiden Texte "Genug gewartet." und "Schluss jetzt." erscheinen, hängt vom exakten Zeitpunkt ab, zu dem der Abbruch den Thread erwischt. Wollen Sie hier etwas mehr Sicherheit, können Sie nach dem Abort die Methode Sleep() aufrufen. Ein Konstrukt zur exakten Steuerung der Reihenfolge zeigt das nächste Beispiel.

Warten auf das Ende. 
Lade Datei 0 Datei 1 Datei 2 Datei 3 Genug gewartet (fertig: False). 
Schluss jetzt.

In der Praxis werden Ihre wartenden Threads oft nicht auf das endgültige Ende eines anderen Threads warten, sondern darauf, dass dieser eine Teilaufgabe sicher erledigt hat. Auch in diesem Fall versetzen Sie einen Thread mittels Join() in den Zustand wartend. Aber anstatt das Ende des anderen Threads abzuwarten, weckt dieser den wartenden Thread durch den Aufruf von Interrupt() (siehe auch Abschnitt 5.2.3, »Unterbrechen mit Interrupt«).

Im folgenden Beispiel wird die Methode Suchen() in einem neuen Thread gestartet. Um die Methode allgemein zu halten, werden ihr alle nötigen Informationen mitgegeben: den suchenden Thread und die zu durchsuchende Zeichenkette. Da die Methode Start() in der Klasse Thread nur ein Objekt vom Typ Object als Übergabeparameter erlaubt, verpacken wir die Information in einem Array mit zwei Objekten. Durch diese Beschränkung sind am Anfang der Methode Suchen() ein paar Typumwandlungen nötig. Sowie in Suchen() ein »e« gefunden wurde, wird der wartende Thread durch Interrupt() geweckt. Die If-Bedingung stellt sicher, dass der Thread nur einmal geweckt wird. Um sicherzustellen, dass nun der Hauptthread in Aktion tritt, bevor der zweite Thread fortfährt, wird dieser durch Join() in den Zustand wartend versetzt. Durch den Interrupt() vor dem Join() in Suchen() wird im Hauptthread die Ausnahme ThreadInterruptedException ausgelöst. Im Catch-Zweig der Methode Test() wird eine Ausgabe erzeugt und der zweite Thread seinerseits mit Interrupt() zum Leben erweckt, sodass dieser mit der Suche fortfahren kann. Im ganzen Programm kommt nicht ein einziger Aufruf von Sleep() vor.


'...\ Multitasking\Zusammenspiel\Ungeduld.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Ungeduld 
    Sub Suchen(ByVal data As Object) 
      Dim thrd As Thread = CType(CType(data, Object())(0), Thread) 
      Dim text As String = CType(CType(data, Object())(1), String) 
      For Each c As Char In text 
        If c = "e"c Then 
          If thrd.ThreadState = ThreadState.WaitSleepJoin Then 
            thrd.Interrupt() 
            Try 
              thrd.Join() 
            Catch ex As ThreadInterruptedException 
            End Try 
          End If 
          Console.WriteLine(c) 
        End If 
      Next 
    End Sub

    Sub Test() 
      Dim textsuche As New Thread(AddressOf Suchen) 
      textsuche.Start(New Object() {Thread.CurrentThread, _ 
                                    "Ein Text mit ein paar ""e""."}) 
      Try 
        textsuche.Join() 
      Catch ex As ThreadInterruptedException 
        Console.WriteLine("Erstes ""e"" gefunden.") 
        textsuche.Interrupt() 
      End Try 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Durch dieses etwas komplexe Wechselspiel verschiedener Aufrufe von Join() und Interrupt() ist sichergestellt, dass die Meldung "Erstes ""e"" gefunden." garantiert vor den Ausgaben der Suchmethode erfolgt. Die einfachere Variante, Aufrufe von Sleep() zu verwenden, könnte unter unglücklichen Umständen diese Garantie nicht geben.

Erstes "e" gefunden. 
e 
e 
e

Rheinwerk Computing - Zum Seitenanfang

5.2.6 Warten erzwingen mit Suspend Zur nächsten ÜberschriftZur vorigen Überschrift

Es gibt extrem selten die Notwendigkeit, einen anderen Thread zum Warten zu zwingen. Die hierfür notwendige Methode Suspend() ist als obsolet gekennzeichnet und sollte nur im Notfall eingesetzt werden. Stattdessen sollten Sie die in Abschnitt 5.3, »Gesicherter Datenaustausch«, beschriebenen Klassen Monitor, Mutex, Event und Semaphore verwenden.


Public Sub Suspend()

Der so angehaltene Thread kann mit Resume() wieder zum Leben erweckt werden:


Public Sub [Resume]()

Das folgende Codefragment definiert eine Methode Report(), die in regelmäßigen Abständen den Wert der Variablen Anzahl ausgibt. Die Methode wird in einem neuen Thread gestartet. Im Hauptthread wird der Wert der Variablen Anzahl ein paar Mal geändert. Vorher wird der zweite Thread mit Suspend() in den Zustand wartend versetzt und danach mit Resume() wieder freigegeben. Schließlich wird der zweite Thread mit Abort() brutal beendet.


Hinweis

Dieses Beispiel schlechter Threadkommunikation steht hier nur der Vollständigkeit halber.



'...\ Multitasking\Zusammenspiel\Zwang.vb

Option Strict On 
Imports System.Threading 
Namespace Multitasking 
  Module Zwang 
    Dim Anzahl As Integer 
    Sub Report() 
      While True 
        Console.Write(Anzahl & " ") 
        Thread.Sleep(10) 
      End While 
    End Sub

    Sub Test() 
      Dim ausgabe As New Thread(AddressOf Report) 
      ausgabe.Start() 
      Thread.Sleep(100) 
      For no As Integer = 0 To 6 
        ausgabe.Suspend() 
        Thread.Sleep(20)           'Simulation umfangreicher Berechnungen 
        Anzahl = Now.Millisecond 
        ausgabe.Resume() 
        Thread.Sleep(0) 
      Next 
      ausgabe.Abort() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Durch das Blockieren schafft es die Methode Report() bei den meisten Läufen nicht, die sieben von Null verschiedenen Ausgaben zu erzeugen.

0 0 0 0 0 0 0 0 0 0 934 934 964 4 4 34 74

Rheinwerk Computing - Zum Seitenanfang

5.2.7 Threadzustände Zur nächsten ÜberschriftZur vorigen Überschrift

Der Zustand eines Threads ist in der schreibgeschützten Eigenschaft ThreadState vom Typ der gleichnamigen Enumeration gespeichert. Die in Tabelle 5.1 aufgelisteten Werte sind Potenzen von 2 und können auch kombiniert werden (siehe Abschnitt 2.7.4, »Bitweise Operatoren«). Lediglich Running hat den Wert 0 und sollte gesondert behandelt werden. Wenn Sie robuste Programme mit mehreren Threads schreiben wollen, werden Sie über kurz oder lang alle diese Zustände berücksichtigen müssen.


Tabelle 5.1 Die Enumeration »System.Threading.ThreadState«

Konstante Beschreibung

Running

Laufend (weder blockiert noch angeforderter Abbruch)

StopRequested

Vorbereitung für das Ende (nur für internen Gebrauch)

SuspendRequested

Zustand direkt nach Suspend()

Background

Gibt an, ob der Thread im Hintergrund läuft.

Unstarted

Noch nicht gestartet.

Stopped

Komplett beendet.

WaitSleepJoin

Zustand während der Sleep()-Zeit sowie nach Join()

Suspended

Der Zustand wartend wurde erreicht.

AbortRequested

Der Zustand direkt nach Abort()

Aborted

Der Zustand nach Abort() ohne ResetAbort()



Rheinwerk Computing - Zum Seitenanfang

5.2.8 Die Klasse Thread topZur vorigen Überschrift

Thread kann nicht beerbt werden, das Verhalten liegt also endgültig fest. Tabelle 5.2 und Tabelle 5.3 listen die öffentlichen Mitglieder als Kurzreferenz. In der Auflistung sind kursiv gesetzte Parameter und Namensteile in eckigen Klammern optional. Um die Liste kurz zu halten, sind die von Object geerbten Mitglieder nicht aufgeführt, ebenso wie die obsoleten Mitglieder ApartmentState(), SetCompressedStack(), GetCompressedStack(), Suspend() und Resume().


Tabelle 5.2 Methoden der Klasse »System.Threading.Thread« (S = Shared)

Methode Beschreibung

New(start As [Parameterized]ThreadStart, maxStackSize As Integer)

Konstruktoren (starten den Thread nicht).

BeginCriticalRegion() EndCriticalRegion()

Threadabbrüche/unbehandelte Ausnahmen können andere Threads gefährden.

S

BeginThreadAffinity() EndThreadAffinity()

Region, die darauf angewiesen ist, dass Aktivitäten nicht den Thread wechseln

S

MemoryBarrier()

Speicherreorganisation nur vor/nach Aufruf erlaubt, nicht übergreifend.

S

GetDomain() As AppDomain GetDomainID() As Integer

Anwendungsdomäne des aktuellen Threads

S

Start(parameter As Object)

In den Zustand bereit wechseln

SpinWait(iterations As Integer)

Schleife für aktuellen Thread ausführen

S

Abort(stateInfo As Object)

Zum Beenden auffordern

ResetAbort()

Abbruch des aktuellen Threads ablehnen

S

Join(msTimeout As Integer) As Boolean Join(timeout As TimeSpan) As Boolean

Aktuellen Thread blockieren, bis der Join-Thread beendet wurde oder die gegebene optionale Zeit verstrichen ist

Sleep(msTimeout As Integer) Sleep(timeout As TimeSpan)

Aktuellen Thread für die angegebene Zeit aussetzen

S

Interrupt()

ThreadInterruptedException in einem wartenden Thread auslösen; hebt die Blockade von Join() und Sleep() auf.

GetApartmentState() As ApartmentState [Try]SetApartmentState( state As ApartmentState) As Boolean

Einen oder mehrere Threads für COM-Anwendungen erlauben

AllocateDS() As LS AllocateNamedDS(name As String) As LS GetNamedDS(name As String) As LS FreeNamedDS(name As String) GetData(slot As LS) As Object SetData(slot As LS, data As Object)

Threadspezifische Datenfelder verwalten. GetData() und SetData() betreffen den aktuellen Thread, die anderen alle Threads.

LS: LocalDataStoreSlot, DS: DataSlot

S

VolatileRead(ByRef address As <datum>) VolatileWrite(ByRef address As <datum>, value As Byte)

Transfer unabhängig von Prozessorcaches. <datum>: alle primitiven Zahlentypen, IntPtr, UIntPtr und Object.

S



Hinweis
Die Steuerung eines Threads mit Suspend() und Resume() sollten Sie unbedingt vermeiden, da Sie nicht wissen, was dieser gerade macht und dadurch sehr leicht andere Threads blockiert werden können.



Tabelle 5.3 Eigenschaften der Klasse »System.Threading.Thread« (R = ReadOnly, S = Shared)

Mitglied Beschreibung

CurrentThread() As Thread

Gerade ausgeführter Thread

RS

CurrentContext() As Context

Aktuelle Umgebung des aktuellen Threads (unter anderem für Sicherheitseinstellungen)

RS

CurrentPrincipal() As IPrincipal

Aktueller Benutzer (rollenbasierte Sicherheit)

S

ManagedThreadId() As Integer

Eindeutige Nummer für den Thread

R

ExecutionContext() As ExecutionContext

Umgebungsinformation zur Interdomänenkommunikation (Sicherheit, Aufruf, …)

R

Priority() As ThreadPriority

Ausführungspriorität

IsAlive() As Boolean

Gestartet und noch nicht terminiert/abgebrochen

R

IsThreadPoolThread() As Boolean

Der Thread ist Teil eines Pools.

R

IsBackground() As Boolean

Der Thread wird im Hintergrund ausgeführt.

ThreadState() As ThreadState

Zustand, unter anderem laufend, bereit, wartend

R

CurrentUICulture() As CultureInfo

ID der zur Laufzeit geladenen Ressourcen

CurrentCulture() As CultureInfo

Länderinformation

Name() As String

Einmalig setzbarer Name




Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual Basic 2008
Visual Basic 2008
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Visual Basic 2012






 Visual Basic 2012


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de