C# Multithreading

ThreadEin Thread ist definiert als der Ausführungspfad eines Programms. Jeder Thread definiert einen eindeutigen Kontrollfluss. Wenn Ihre Anwendung komplexe und zeitaufwändige Vorgänge umfasst, ist es oft von Vorteil, verschiedene Thread-Ausführungspfade einzurichten, wobei jeder Thread eine bestimmte Arbeit ausführt.

Thread istLeichtgewichtiger ProzessEin häufiges Beispiel für die Verwendung von Threads ist die Implementierung von paralleler Programmierung in modernen Betriebssystemen. Die Verwendung von Threads vermeidet die Verschwendung von CPU-Zyklen und verbessert die Effizienz von Anwendungen.

Die Programme, die wir bisher geschrieben haben, laufen als einzelner Prozess mit einem einzelnen Thread als laufende Instanz der Anwendung. Auf diese Weise kann die Anwendung jedoch nur eine Aufgabe gleichzeitig ausführen. Um mehrere Aufgaben gleichzeitig auszuführen, kann sie in kleinere Threads aufgeteilt werden.

Thread-Lebenszyklus

Der Thread-Lebenszyklus beginnt, wenn ein Objekt der Klasse System.Threading.Thread erstellt wird, und endet, wenn der Thread beendet wird oder seine Ausführung abgeschlossen hat.

Die verschiedenen Zustände im Thread-Lebenszyklus sind unten aufgeführt:

  • Nicht gestarteter Zustand: Der Zustand, in dem eine Thread-Instanz erstellt wurde, aber die Start-Methode nicht aufgerufen wurde.
  • Bereit-Zustand: Der Zustand, in dem ein Thread bereit zur Ausführung ist und auf einen CPU-Zyklus wartet.
  • Nicht ausführbarer Zustand: Ein Thread ist in den folgenden Fällen nicht ausführbar:
    • Die Sleep-Methode wurde aufgerufen
    • Die Wait-Methode wurde aufgerufen
    • Blockiert durch einen E/A-Vorgang
  • Toter Zustand: Der Zustand, in dem der Thread die Ausführung abgeschlossen hat oder abgebrochen wurde.

Hauptthread

In C# wirdSystem.Threading.Thread– die Thread-Klasse – für Thread-Arbeit verwendet. Sie ermöglicht das Erstellen und den Zugriff auf einzelne Threads in einer Multithread-Anwendung. Der erste in einem Prozess ausgeführte Thread wird als Hauptthread bezeichnet.Hauptthread。

Wenn ein C#-Programm mit der Ausführung beginnt, wird der Hauptthread automatisch erstellt. Mit derThread-Klasse erstellte Threads werden als Kindthreads des Hauptthreads bezeichnet. Sie können dieCurrentThread-Eigenschaft der Thread-Klasse verwenden, um auf den Thread zuzugreifen.

Das folgende Programm demonstriert die Ausführung des Hauptthreads:

Beispiel

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class MainThreadProgram
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "MainThread";
            Console.WriteLine("This is {0}", th.Name);
            Console.ReadKey();
        }
    }
}

Wenn der obige Code kompiliert und ausgeführt wird, liefert er die folgenden Ergebnisse:

This is MainThread

Allgemeine Eigenschaften und Methoden der Thread-Klasse

Die folgende Tabelle listet einige allgemeine Methoden der KlasseThreadund dieEigenschaft:

EigenschaftBeschreibung
CurrentContextRuft den aktuellen Kontext ab, in dem der Thread ausgeführt wird.
CurrentCultureRuft die Kultur des aktuellen Threads ab oder legt sie fest.
CurrentPrincipalRuft den aktuellen Prinzipal des Threads ab oder legt ihn fest (für rollenbasierte Sicherheit).
CurrentThreadRuft den aktuell ausgeführten Thread ab.
CurrentUICultureRuft die aktuelle Kultur ab oder legt sie fest, die vom Resource Manager verwendet wird, um zur Laufzeit kulturspezifische Ressourcen nachzuschlagen.
ExecutionContextRuft ein ExecutionContext-Objekt ab, das Kontextinformationen über den aktuellen Thread enthält.
IsAliveRuft einen Wert ab, der den Ausführungsstatus des aktuellen Threads angibt.
IsBackgroundRuft einen Wert ab oder legt einen Wert fest, der angibt, ob ein Thread ein Hintergrundthread ist.
IsThreadPoolThreadRuft einen Wert ab, der angibt, ob ein Thread zum verwalteten Threadpool gehört.
ManagedThreadIdRuft den eindeutigen Bezeichner des aktuellen verwalteten Threads ab.
NameRuft den Namen des Threads ab oder legt ihn fest.
PrioritätRuft einen Wert ab oder legt einen Wert fest, der die Planungspriorität des Threads angibt.
ThreadStateRuft einen Wert ab, der den Zustand des aktuellen Threads enthält.

Die folgende Tabelle enthältThreadEinige allgemeine Methoden der KlasseMethoden:

Laufende NummerMethodenname & Beschreibung
1public void Abort()
Löst eine ThreadAbortException für den Thread aus, für den diese Methode aufgerufen wird, um den Vorgang des Beendens dieses Threads einzuleiten. Der Aufruf dieser Methode beendet normalerweise den Thread.
2public static LocalDataStoreSlot AllocateDataSlot()
Reserviert einen unbenannten Datenslot für alle Threads. Verwenden Sie für bessere Leistung stattdessen Felder, die mit dem Attribut ThreadStaticAttribute gekennzeichnet sind.
3public static LocalDataStoreSlot AllocateNamedDataSlot( string name)
Reserviert einen benannten Datenslot für alle Threads. Verwenden Sie für bessere Leistung stattdessen Felder, die mit dem Attribut ThreadStaticAttribute gekennzeichnet sind.
4public static void BeginCriticalRegion()
Benachrichtigt den Host, dass die Ausführung gerade einen Codebereich betritt, in dem die Auswirkungen von Threadabbrüchen oder nicht behandelten Ausnahmen andere Aufgaben in der Anwendungsdomäne gefährden können.
5public static void BeginThreadAffinity()
Benachrichtigt den Host, dass verwalteter Code dabei ist, Anweisungen auszuführen, die von der Identität des aktuellen physischen Betriebssystemthreads abhängen.
6public static void EndCriticalRegion()
Benachrichtigt den Host, dass die Ausführung gerade einen Codebereich betritt, in dem Threadabbrüche oder nicht behandelte Ausnahmen nur die aktuelle Aufgabe betreffen.
7public static void EndThreadAffinity()
Benachrichtigt den Host, dass verwalteter Code die Ausführung von Anweisungen abgeschlossen hat, die von der Identität des aktuellen physischen Betriebssystemthreads abhängen.
8public static void FreeNamedDataSlot(string name)
Beseitigt die Zuordnung zwischen Namen und Slots für alle Threads im Prozess. Verwenden Sie für bessere Leistung stattdessen Felder, die mit dem ThreadStaticAttribute-Attribut gekennzeichnet sind.
9public static Object GetData( LocalDataStoreSlot slot )
Ruft den Wert aus dem angegebenen Slot im aktuellen Thread innerhalb der aktuellen Domäne des aktuellen Threads ab. Verwenden Sie für bessere Leistung stattdessen Felder, die mit dem ThreadStaticAttribute-Attribut gekennzeichnet sind.
10public static AppDomain GetDomain()
Gibt die aktuelle Domäne zurück, in der der aktuelle Thread ausgeführt wird.
11public static AppDomain GetDomainID()
Gibt einen eindeutigen Anwendungsdomänenbezeichner zurück.
12public static LocalDataStoreSlot GetNamedDataSlot( string name )
Sucht einen benannten Datenslot nach. Verwenden Sie für bessere Leistung stattdessen Felder, die mit dem ThreadStaticAttribute-Attribut gekennzeichnet sind.
13public void Interrupt()
Unterbricht einen Thread, der sich im Threadzustand WaitSleepJoin befindet.
14public void Join()
Blockiert den aufrufenden Thread, bis ein Thread beendet wird, während weiterhin das standardmäßige COM- und SendMessage-Nachrichtenpumping ausgeführt wird. Diese Methode weist verschiedene Überladungsformen auf.
15public static void MemoryBarrier()
Synchronisiert den Speicherzugriff wie folgt: Der Prozessor, der den aktuellen Thread ausführt, kann Anweisungen nicht so umordnen, dass Speicherzugriffe nach dem MemoryBarrier-Aufruf vor Speicherzugriffen ausgeführt werden, die vor dem MemoryBarrier-Aufruf erfolgen.
16public static void ResetAbort()
Hebt den für den aktuellen Thread angeforderten Abort auf.
17public static void SetData( LocalDataStoreSlot slot, Object data )
Legt Daten im angegebenen Slot im aktuell ausgeführten Thread für die aktuelle Domäne des Threads fest. Verwenden Sie für eine bessere Leistung stattdessen Felder, die mit dem ThreadStaticAttribute-Attribut markiert sind.
18public void Start()
Startet einen Thread.
19public static void Sleep( int millisecondsTimeout )
Pausiert den Thread für einen bestimmten Zeitraum.
20public static void SpinWait( int iterations )
Bewirkt, dass ein Thread für die durch den Parameter iterations definierte Zeitspanne wartet.
21public static byte VolatileRead( ref byte address )
public static double VolatileRead( ref double address )
public static int VolatileRead( ref int address )
public static Object VolatileRead( ref Object address )

Liest den Feldwert. Unabhängig von der Anzahl der Prozessoren oder dem Zustand des Prozessor-Caches ist der Wert der zuletzt von einem beliebigen Prozessor im Computer geschriebene Wert. Diese Methode weist verschiedene Überladungsformen auf. Hier werden nur einige Formen angegeben.
22public static void VolatileWrite( ref byte address, byte value )
public static void VolatileWrite( ref double address, double value )
public static void VolatileWrite( ref int address, int value )
public static void VolatileWrite( ref Object address, Object value )

Schreibt sofort einen Wert in ein Feld, sodass der Wert für alle Prozessoren im Computer sichtbar ist. Diese Methode hat verschiedene Überladungsformen. Hier sind nur einige Formen angegeben.
23public static bool Yield()
Bewirkt, dass der aufrufende Thread einen anderen Thread ausführt, der bereit ist, auf dem aktuellen Prozessor ausgeführt zu werden. Der auszuführende Thread wird vom Betriebssystem ausgewählt.

Threads erstellen

Threads werden durch Erweitern der Thread-Klasse erstellt. Die erweiterte Thread-Klasse ruft dieStart()-Methode auf, um die Ausführung des untergeordneten Threads zu starten.

Das folgende Programm veranschaulicht dieses Konzept:

Beispiel

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Untergeordneter Thread startet");
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Untergeordneten Thread erstellen");
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }
}

Wenn der obige Code kompiliert und ausgeführt wird, liefert er die folgenden Ergebnisse:

In Main: Creating the Child thread
Child thread starts

Threads verwalten

Die Thread-Klasse bietet verschiedene Methoden zum Verwalten von Threads.

Das folgende Beispiel demonstriertsleep()Die Verwendung der Methode, um einen Thread für eine bestimmte Zeitspanne anzuhalten.

Beispiel

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Untergeordneter Thread startet");
            // Den Thread für 5000 Millisekunden anhalten
            int sleepfor = 5000;
            Console.WriteLine("Untergeordneter Thread für {0} Sekunden pausiert",
                              sleepfor / 1000);
            Thread.Sleep(sleepfor);
            Console.WriteLine("Untergeordneter Thread wird fortgesetzt");
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Untergeordneten Thread erstellen");
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }
}

Wenn der obige Code kompiliert und ausgeführt wird, liefert er die folgenden Ergebnisse:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

Threads zerstören

Abort()-Methode wird verwendet, um den Thread zu zerstören.

Durch das Auslösen vonthreadabortexceptionwird der Thread zur Laufzeit abgebrochen. Diese Ausnahme kann nicht abgefangen werden. Wenn einfinally-Block vorhanden ist, wird die Steuerung anfinally übergeben.Block.

Das folgende Programm veranschaulicht dies:

Beispiel

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            try
            {

                Console.WriteLine("Untergeordneter Thread startet");
                // Bis 10 zählen
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine("Untergeordneter Thread abgeschlossen");

            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine("Thread-Abbruch-Ausnahme");
            }
            finally
            {
                Console.WriteLine("Thread-Ausnahme konnte nicht abgefangen werden");
            }

        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Untergeordneten Thread erstellen");
            Thread childThread = new Thread(childref);
            childThread.Start();
            // Den Hauptthread für eine gewisse Zeit anhalten
            Thread.Sleep(2000);
            // Jetzt den untergeordneten Thread abbrechen
            Console.WriteLine("In Main: Untergeordneter Thread wird abgebrochen");
            childThread.Abort();
            Console.ReadKey();
        }
    }
}

Wenn der obige Code kompiliert und ausgeführt wird, liefert er die folgenden Ergebnisse:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception 
Weitere Erweiterungen