Topics

  1. Managed Threading
  2. Thread Synchronizing
  3. Asynchron Programming Design Patterns

Namespaces

  • System
  • System.Threading

Grundlagen

  • Der Mainthread ist ein Vordergrundthread, er hält den Prozess am "Leben".
  • Hintergrundthreads halten den Process nicht am Leben. Alle Hintergrundthread werden beim beenden des Vordergrundthreads beendet.
  • Thread-Instanzen haben eine IsBackground-Eigenschaft, um den Thread als Hintergrundthread zu starten. ThreadPool.QueuedUserWorkItem Threads sind immer Hintergrundthreads.

Threadpool

Mehrere Threads starten

Um Threads innerhalb seiner Anwendung zu starten ist die Klasse ThreadPool und vorallem die darin anthaltene statische Methode QueueUserWorkItem eine mögliche Variante. Die mit der Methode erstellten Threads sind Hintergrund-Threads. D.h. die Eigenschaft IsBackground der Thread-Instanz ist auf true gesetzt. Zudem werden alle Backgroundthreads beim Beenden des "auslösenden" Vordergrundsthread automatische beendendet. Der hierbei auftretende ungültige Zustand ist eine einfache Form einer Race Condition. Threadpool wird häufig für Threads verwendet die in kleiner und gar keiner Kommunikation mit dem Vordergrundthread stehen.

Hinweise
  • 25 Worker Threads per default (CLR2.0), ab SP1 250
  • SetMaxThreads
  • Thread werden nach der Abarbeitung recycelt
  • GetAvailable Threads


Threads verwalten

Klasse

System.Threading.Thread

Ein Thread kann eine Methode über zwei verschiedene Delegaten ausführen
  • ThreadStart: ohne Parameter
  • ParameterizedThreadStart: mit einen Object-Typ Parameter
Ein Thread wird Instanziert und übergibt ein Delegaten an den Konstruktor

Über den Aufruf der Methode Start() wird der Thread gestartet
  • Der neu gestartete Thread beendet sich automatisch nach der erfolgreichen Beendigung der Funktion oder nach Aufruf der Methode Abort()
Eine Instanz eines Threads hat mehrere Methoden zum senden verschiedener Kommandos. Hier nur zwei der wichtigsten
  • Join(), Join(int timeout) - Blockt den aktuellen Thread bis der gestartete neue Thread zurück zum Aufrufer kommt. Die Überladung gibt zusätzlichen einen Timeout in Millisekunden an.
  • Abort() - Throw a ThreadAbortException - bricht den Thread ab
Als statische Methoden existieren zwei wichtige
  • Thread.Sleep(int timeout) - Thread macht eine Pause in Millisekunden, hier ist Wechsel des Kontextes erlaubt
  • Thread.SpinWait(int iteration) - Thread wartet eine Anzahl von Iterationen. Diese Methode ist für die Thread-Synchronisierung mit bzw. Monitor.Enter() nützlich, hier ist ein Kontextwechsel nicht erlaubt

Hinweise

Der gesamte ExecutionContext, d.h. Security, Localization, Transaction - Informationen werden automatisch an den neuen Thread übertragen


Thread Synchronizing

Grundsätzlich geht es bei der Thread-Synchronisierung um den Zugriff auf Resourcen durch mehrere Threads. Zur Synchronisierung von Thread gibt es mehrere Möglichkeiten.
  • Monitor - exklusive Sperre für Referenz-Typen. Wert-Typen werden nicht gesperrt. Wenn ein Wert-Typ gesperrt werden muss, muss ein Referenz-Typ erstellt werden und dieser gesperrt werden
  • ReaderWriterLockSlim - Lese/Schreibe-Sperre nach dem Flag-Prinzip (Sperre gilt nicht auf Resourcen)
  • Interlocked - exklusive Sperre auf Int32 und Int64

Optimieren von sperren auf Collections

Beim synchronisierten Zugriff auf Collections bieten viele Collections den eingebauten Mechanismus diese in einen gesperrten Zustand zu versetzen. Ein
lock(ICollection.SyncRoot) bietet diese Möglichkeit für einen optimierten Zugriff. ICollection.IsSyncronized gibt den Zustand der Collection wieder.


Monitor vs. Lock

In C# wird für die Implementierung der Monitor Sperre ein spezielles Schlüsselwort - lock - bereitgestellt. Dieses bildet intern ein Monitor.Enter(lockObject) ab. Sie sind nach dem Compilerlauf identisch, es wird der selbe IL Code erzeugt.


ReaderWriterLockSlim

Mit der ReaderWriterLockSlim Klasse können ebenfalls die Zugriffe von verschiedenen Threads auf eine gemeinsame Resource abgesichert werden. Mehrere Threads können hierbei Lesezugriff oder einen exklusiven Schreibzugriff erhalten. Leser und Schreiber werden bei diese Methode der Sperre separiert gequeued. Ein Schreiber erhält einen exklusiven Zugriff auf die Resource solange keine Leser eine Sperre auf das Objekt hat. Hier wird abwechselnd zwischen den Lesern und einem Schreiber gewechselt.


Auf fertige Thread warten

Sollte auf die Abarbeitung eines einzigen Thread gewarted werden müssen, sollte ein Aufruf der Instanzmethode Join auf dem Thread-Objekt ausreichen. Wenn auf mehrere Threads gewarted werden muss, bevor die weitere Abarbeitung erfolgt, bringt die statische Methode WaitHandle.WaitAll mit der Übergabe eines WaitHandle-Objekt-Arrays die gewünschte Hilfe mit. Mit der Instanzmethode WaitOne() von Thread-Objekten kann auf den signalisierten Zustand eines Thread aus einem anderen Thread gewartet werden. Ein Timeout kann hier ebenfalls Deadlocks bzw. Race Conditions verhindern.

WaitHandle Klassen - Kommunikation über Signalzustände zwischen Threads

Die WaitHandle-Klassen stellen einen weiteren Synchronisierungsmechanismus dar. Hier wird über ein Signal (signalisierter Zustand über Set()-Methode) bzw. ein Systemsynchronisierungsereigniss der aktuelle Zustand des Handles dem Aufrufer mitgeteilt. Es gibt zwei WaitHandles AutoResetEvent und ManualResetEvent. Mit dem AutoResetEvent wird der Thread nach seiner Abarbeitung freigegeben und das Signal automatisch zurückgesetzt. Beim ManualResetEvent muss das Signal mit Reset() manuell zurückgesetzt werden (unsignalisierter Zustand), um den Thread freizugeben.