NET definiert Multithreading-Funktionalität im System.Threading-Namespace. Um Multithreading zu verwenden, müssen Sie daher zunächst einen Verweis auf diesen Namespace deklarieren (mithilfe von System.Threading;).
a. Einen Thread starten bedeutet, wie der Name schon sagt, einen Thread zu erstellen und zu starten:
Thread thread1 = new Thread(new ThreadStart(Count));
Dabei ist Count die Funktion, die vom neuen Thread ausgeführt werden soll.
b. Töte den Thread
Beim „Töten eines Threads“ geht es darum, einen Thread zu löschen. Um Ihre Bemühungen nicht zu verschwenden, ist es am besten, vor dem Beenden eines Threads festzustellen, ob er noch aktiv ist (über das IsAlive-Attribut), und dann die Abort-Methode aufzurufen, um den Thread zu beenden .
c. Den Thread anzuhalten bedeutet, einen laufenden Thread für einen bestimmten Zeitraum ruhen zu lassen. Beispielsweise soll thread.Sleep(1000); den Thread 1 Sekunde lang schlafen lassen.
d. Priorität bedarf keiner Erklärung. Das hreadPriority-Attribut in der Thread-Klasse wird zum Festlegen der Priorität verwendet. Es gibt jedoch keine Garantie dafür, dass das Betriebssystem die Priorität akzeptiert. Die Priorität eines Threads kann in 5 Typen unterteilt werden: Normal, AboveNormal, BelowNormal, Highest und Lowest. Konkrete Implementierungsbeispiele sind wie folgt:
thread.Priority = ThreadPriority.Highest;
e. Thread anhalten
Die Suspend-Methode der Thread-Klasse wird verwendet, um den Thread anzuhalten, bis Resume aufgerufen wird, bevor der Thread weiter ausgeführt werden kann. Wenn der Thread bereits angehalten ist, funktioniert das nicht.
if (thread.ThreadState = ThreadState.Running)
{
thread.Suspend();
}
f. Der Wiederherstellungsthread wird verwendet, um den angehaltenen Thread fortzusetzen, damit er weiter ausgeführt werden kann. Wenn der Thread nicht angehalten ist, funktioniert er nicht.
if (thread.ThreadState = ThreadState.Suspended)
{
thread.Resume();
}
Nachfolgend ist ein Beispiel aufgeführt, um die einfache Threading-Funktionalität zu veranschaulichen. Dieses Beispiel stammt aus der Hilfedokumentation.
Verwenden des Systems;
Verwenden von System.Threading;
// Einfaches Threading-Szenario: Starten Sie die Ausführung einer statischen Methode
// in einem zweiten Thread.
öffentliche Klasse ThreadExample {
// Die ThreadProc-Methode wird aufgerufen, wenn der Thread startet.
// Es wird zehnmal wiederholt, schreibt in die Konsole und gibt nach
// jedes Mal den Rest seiner Zeitscheibe und endet dann.
public static void ThreadProc() {
for (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// Den Rest der Zeitscheibe ausgeben.
Thread.Sleep(0);
}
}
public static void Main() {
Console.WriteLine("Hauptthread: Starten Sie einen zweiten Thread.");
// Der Konstruktor für die Thread-Klasse erfordert einen ThreadStart
// Delegat, der die Methode darstellt, die auf dem ausgeführt werden soll
// Thread. C# vereinfacht die Erstellung dieses Delegaten.
Thread t = new Thread(new ThreadStart(ThreadProc));
// ThreadProc starten. Auf einem Uniprozessor wird der Thread nicht abgerufen
// jede Prozessorzeit, bis der Hauptthread nachgibt
// den Thread.Sleep, der auf t.Start() folgt, um den Unterschied zu sehen.
t.Start();
//Thread.Sleep(0);
for (int i = 0; i < 4; i++) {
Console.WriteLine("Hauptthread: Erledige etwas Arbeit.");
Thread.Sleep(0);
}
Console.WriteLine("Hauptthread: Join() aufrufen, um zu warten, bis ThreadProc endet.");
t.Join();
Console.WriteLine("Hauptthread: ThreadProc.Join ist zurückgekehrt. Drücken Sie die Eingabetaste, um das Programm zu beenden.");
Console.ReadLine();
}
}
Dieser Code erzeugt eine Ausgabe ähnlich der folgenden:
Hauptthread: Starten Sie einen zweiten Thread.
Hauptthread: Mach etwas Arbeit.
ThreadProc: 0
Hauptthread: Mach etwas Arbeit.
ThreadProc: 1
Hauptthread: Mach etwas Arbeit.
ThreadProc: 2
Hauptthread: Mach etwas Arbeit.
ThreadProc: 3
Hauptthread: Rufen Sie Join() auf, um zu warten, bis ThreadProc endet.
ThreadProc: 4
ThreadProc: 5
ThreadProc: 6
ThreadProc: 7
ThreadProc: 8
ThreadProc: 9
Hauptthread: ThreadProc.Join ist zurückgekehrt. Drücken Sie die Eingabetaste, um das Programm zu beenden.
In Visul C# stellt der System.Threading-Namespace einige Klassen und Schnittstellen bereit, die Multithread-Programmierung ermöglichen. Es gibt drei Methoden zum Erstellen von Threads: Thread, ThreadPool und Timer. Hier finden Sie eine kurze Einführung, wie Sie sie einzeln verwenden können.
1. Thread
Dies ist möglicherweise die komplexeste Methode, bietet jedoch eine Vielzahl flexibler Steuerungsmöglichkeiten für Threads. Zuerst müssen Sie seinen Konstruktor verwenden, um eine Thread-Instanz zu erstellen, mit nur einem ThreadStart-Delegaten: public Thread(ThreadStart start); um seine laufende Priorität festzulegen oder abzurufen (Enum ThreadPriority: Normal, Lowest, Highest, BelowNormal, AboveNormal).
Das folgende Beispiel generiert zunächst zwei Thread-Instanzen t1 und t2, legt dann jeweils ihre Prioritäten fest und startet dann die beiden Threads (die beiden Threads sind grundsätzlich gleich, außer dass ihre Ausgabe unterschiedlich ist, t1 ist „1“ und t2 ist „2“) "Anhand des Verhältnisses der Anzahl der von ihnen ausgegebenen Zeichen können wir ungefähr das Verhältnis der von ihnen belegten CPU-Zeit erkennen, was auch ihre jeweiligen Prioritäten widerspiegelt.)
static void Main(string[] args)
{
Thread t1 = neuer Thread(new ThreadStart(Thread1));
Thread t2 = neuer Thread(new ThreadStart(Thread2));
t1.Priority = ThreadPriority.BelowNormal;
t2.Priority = ThreadPriority.Lowest;
t1.Start();
t2.Start();
}
öffentliches statisches void Thread1()
{
for (int i = 1; i < 1000; i++)
{//Schreiben Sie jedes Mal eine „1“, wenn eine Schleife ausgeführt wird.
dosth();
Console.Write("1");
}
}
öffentliches statisches void Thread2()
{
for (int i = 0; i < 1000; i++)
{//Schreiben Sie jedes Mal eine „2“, wenn eine Schleife ausgeführt wird.
dosth();
Console.Write("2");
}
}
öffentliche statische Leere dosth()
{//Wird zur Simulation komplexer Vorgänge verwendet
für (int j = 0; j < 10000000; j++)
{
int a=15;
a = a*a*a*a;
}
}
Das Ergebnis der Ausführung des obigen Programms ist:
111111111111111111111111111111111111111121111111111111111111111111111111111111111112
111111111111111111111111111111111111111121111111111111111111111111111111111111111112
111111111111111111111111111111111111111121111111111111111111111111111111111111111112
Aus den obigen Ergebnissen können wir ersehen, dass der t1-Thread viel mehr CPU-Zeit beansprucht als t2. Dies liegt daran, dass die Priorität von t1 höher ist als die von t2. Wenn wir die Prioritäten von t1 und t2 auf Normal setzen, sind die Ergebnisse sind wie folgt bild:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
Aus dem obigen Beispiel können wir ersehen, dass seine Struktur dem Win32-Worker-Thread ähnelt, aber einfacher ist. Sie müssen nur die vom Thread aufzurufende Funktion als Delegaten verwenden und den Delegaten dann als Parameter verwenden Erstellen Sie eine Thread-Instanz. Wenn Start() zum Starten aufgerufen wird, wird die entsprechende Funktion aufgerufen und die Ausführung beginnt in der ersten Zeile dieser Funktion.
Als nächstes kombinieren wir die ThreadState-Eigenschaft des Threads, um die Steuerung des Threads zu verstehen. ThreadState ist ein Aufzählungstyp, der den Status des Threads widerspiegelt. Wenn eine Thread-Instanz zum ersten Mal erstellt wird, ist ihr ThreadState Unstarted; wenn der Thread durch Aufrufen von Start() gestartet wird, ist ihr ThreadState Running. Wenn Sie möchten, dass er angehalten (blockiert) wird, können Sie Thread aufrufen. Bei der Sleep()-Methode gibt es zwei überladene Methoden (Sleep(int), Sleep(Timespan)), die lediglich unterschiedliche Formate zur Darstellung der Zeitspanne darstellen. Wenn diese Funktion in einem Thread aufgerufen wird, bedeutet dies, dass der Thread blockiert für einen bestimmten Zeitraum (die Zeit wird durch die Anzahl der Millisekunden oder die Zeitspanne bestimmt, die an Sleep übergeben wird, aber wenn der Parameter 0 ist, bedeutet dies, dass dieser Thread angehalten wird, um die Ausführung anderer Threads zu ermöglichen, und Infinite angibt, um den Thread auf unbestimmte Zeit zu blockieren), um Dieses Mal wird sein ThreadState zu WaitSleepJoin. Eine weitere erwähnenswerte Sache ist, dass die Sleep()-Funktion als statisch definiert ist. ! Dies bedeutet auch, dass es nicht in Verbindung mit einer Thread-Instanz verwendet werden kann, dh es gibt keinen ähnlichen Aufruf wie t1.Sleep(10)! Genauso kann die Sleep()-Funktion nur von dem Thread aufgerufen werden, der selbst „schlafen“ muss, und darf nicht von anderen Threads aufgerufen werden. Ebenso ist es eine persönliche Angelegenheit, die nicht von anderen entschieden werden kann . Wenn sich ein Thread jedoch im WaitSleepJoin-Zustand befindet und ihn aktivieren muss, können Sie die Thread.Interrupt-Methode verwenden, die eine ThreadInterruptedException für den Thread auslöst (beachten Sie die aufrufende Methode von Sleep):
static void Main(string[] args)
{
Thread t1 = neuer Thread(new ThreadStart(Thread1));
t1.Start();
t1.Interrupt();
E.WaitOne();
t1.Interrupt();
t1.Join();
Console.WriteLine(“t1 ist Ende”);
}
static AutoResetEvent E = new AutoResetEvent(false);
öffentliches statisches void Thread1()
{
versuchen
{//Aus den Parametern ist ersichtlich, dass es zu einem Ruhezustand kommt
Thread.Sleep(Timeout.Infinite);
}
Catch(System.Threading.ThreadInterruptedException e)
{//Interrupt-Handler
Console.WriteLine (" 1. Interrupt");
}
E.Set();
versuchen
{// schlafen
Thread.Sleep(Timeout.Infinite);
}
Catch(System.Threading.ThreadInterruptedException e)
{
Console.WriteLine (" 2. Interrupt");
}//Pause für 10 Sekunden
Thread.Sleep (10000);
}
Das laufende Ergebnis ist: 1. Interrupt
2. Unterbrechung
(Nach 10s) ist t1 zu Ende
Aus dem obigen Beispiel können wir ersehen, dass die Thread.Interrupt-Methode das Programm aus einem blockierenden Zustand (WaitSleepJoin) aufwecken und in den entsprechenden Interrupt-Handler eintreten und dann die Ausführung fortsetzen kann (sein ThreadState ändert sich auch in Running). Funktion muss Beachten Sie folgende Punkte:
1. Diese Methode kann nicht nur die durch Sleep verursachte Blockierung aufwecken, sondern ist auch für alle Methoden wirksam, die dazu führen können, dass der Thread in den WaitSleepJoin-Status wechselt (z. B. Wait und Join). Wie im obigen Beispiel gezeigt, sollten Sie bei der Verwendung die Methode, die die Thread-Blockierung verursacht, in den Try-Block und den entsprechenden Interrupt-Handler in den Catch-Block einfügen.
2. Rufen Sie Interrupt für einen bestimmten Thread auf, wenn er sich im WaitSleepJoin-Status befindet. Befindet er sich zu diesem Zeitpunkt nicht im WaitSleepJoin-Status, wird er sofort unterbrochen, wenn er in diesen Status wechselt . Wenn Interrupt vor der Unterbrechung mehrmals aufgerufen wird, ist nur der erste Aufruf gültig. Aus diesem Grund habe ich im obigen Beispiel die Synchronisierung verwendet, um sicherzustellen, dass der zweite Aufruf von Interrupt nach dem ersten Interrupt aufgerufen wird, andernfalls kann es zu einem zweiten Aufruf kommen Der Aufruf hat keine Wirkung (wenn er vor dem ersten Interrupt aufgerufen wird). Sie können versuchen, die Synchronisierung zu entfernen. Das Ergebnis ist wahrscheinlich: 1. Interrupt
Das obige Beispiel verwendet außerdem zwei weitere Methoden, um den Thread in den WaitSleepJoin-Zustand zu versetzen: die Verwendung des Synchronisationsobjekts und der Thread.Join-Methode. Die Verwendung der Join-Methode ist relativ einfach. Dies bedeutet, dass der aktuelle Thread, der diese Methode aufruft, blockiert, bis der andere Thread (in diesem Beispiel t1) beendet wird oder die angegebene Zeit verstrichen ist (falls vorhanden). Wenn eine Bedingung (falls vorhanden) auftritt, wird der WaitSleepJoin-Status sofort beendet und in den Running-Status versetzt (die Bedingung kann anhand des Rückgabewerts der .Join-Methode ermittelt werden. Wenn sie wahr ist, wird der Thread beendet. Wenn sie falsch ist, die Zeit ist abgelaufen). Der Thread kann auch mit der Thread.Suspend-Methode angehalten werden. Wenn sich ein Thread im Running-Status befindet und die Suspend-Methode für ihn aufgerufen wird, wechselt er in den SuspendRequested-Status, wird jedoch nicht sofort angehalten, bis der Thread einen Safe erreicht Der Thread bleibt hängen und wechselt dann in den Suspended-Zustand. Wenn er für einen Thread aufgerufen wird, der bereits angehalten ist, ist er ungültig. Um den Vorgang fortzusetzen, rufen Sie einfach Thread.Resume auf.
Das Letzte, worüber wir sprechen, ist die Zerstörung von Threads. Wir können die Abort-Methode für den Thread aufrufen, der zerstört werden muss, und sie wird eine ThreadAbortException für diesen Thread auslösen. Wir können Code im Thread in den Try-Block einfügen und den entsprechenden Verarbeitungscode in den entsprechenden Catch-Block einfügen. Wenn der Thread den Code im Try-Block ausführt, springt er beim Aufruf von Abort in den entsprechenden Catch-Block . Wird es innerhalb des Catch-Blocks ausgeführt, wird es beendet, nachdem der Code im Catch-Block ausgeführt wurde (wenn ResetAbort innerhalb des Catch-Blocks ausgeführt wird, ist es anders: Es bricht die aktuelle Abort-Anfrage ab und fährt mit der Ausführung nach unten fort. Wenn Sie also Wenn Sie sicherstellen möchten, dass ein Thread beendet wird, verwenden Sie am besten Join (wie im obigen Beispiel).
2. ThreadPool
Thread-Pool (ThreadPool) ist eine relativ einfache Methode. Sie eignet sich für kurze Aufgaben, die mehrere Threads erfordern (z. B. einige Threads, die häufig blockiert werden). Der Nachteil besteht darin, dass die erstellten Threads nicht gesteuert werden können. Da jeder Prozess nur einen Thread-Pool hat und natürlich jede Anwendungsdomäne nur einen Thread-Pool (Zeile) hat, werden Sie feststellen, dass die Mitgliedsfunktionen der ThreadPool-Klasse alle statisch sind! Wenn Sie ThreadPool.QueueUserWorkItem, ThreadPool.RegisterWaitForSingleObject usw. zum ersten Mal aufrufen, wird eine Thread-Pool-Instanz erstellt. Das Folgende ist eine Einführung in die beiden Funktionen im Thread-Pool:
public static bool QueueUserWorkItem( //Gibt true zurück, wenn der Aufruf erfolgreich ist
WaitCallback callBack,//Der Delegat, der vom zu erstellenden Thread aufgerufen wird
Objektstatus // An den Delegaten übergebene Parameter
)//Eine andere überladene Funktion ist ähnlich, außer dass der Delegat keine Parameter akzeptiert. Die Funktion dieser Funktion besteht darin, den zu erstellenden Thread in die Warteschlange für den Thread-Pool zu stellen, wenn die Anzahl der verfügbaren Threads im Thread-Pool nicht Null ist. Der Thread-Pool hat Threads erstellt. Wenn die Anzahl begrenzt ist (der Standardwert ist 25), wird dieser Thread erstellt. Andernfalls wird er in die Warteschlange des Thread-Pools gestellt und gewartet, bis Threads verfügbar sind.
öffentliches statisches RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject,//WaitHandle muss registriert werden
WaitOrTimerCallback callBack, // Thread-Aufrufdelegierter
Objektstatus, // Parameter, die an den Delegaten übergeben werden
int TimeOut,//Timeout, Einheit ist Millisekunden,
boolexecuteOnlyOnce file:// Gibt an, ob nur einmal ausgeführt werden soll
);
öffentlicher Delegat void WaitOrTimerCallback(
Objektstatus, //das heißt, die an den Delegaten übergebenen Parameter
bool timedOut//true bedeutet aufgrund eines Timeout-Aufrufs, andernfalls bedeutet es waitObject
);
Die Funktion dieser Funktion besteht darin, einen Wartethread zu erstellen. Sobald diese Funktion aufgerufen wird, befindet sie sich im Status „Blockiert“, bis der Parameter „waitObject“ in den beendeten Status wechselt Es ist erwähnenswert, dass sich dieses „Blockieren“ stark vom WaitSleepJoin-Status von Thread unterscheidet: Wenn sich ein Thread im WaitSleepJoin-Status befindet, wird er von der CPU regelmäßig aktiviert, um die Statusinformationen abzufragen und zu aktualisieren, und wechselt dann erneut in den WaitSleepJoin-Status. Der Thread-Wechsel ist sehr ressourcenintensiv. Der mit dieser Funktion erstellte Thread wechselt nicht zu diesem Thread, bevor er ausgeführt wird. Es nimmt weder CPU-Zeit in Anspruch, noch verschwendet er Zeit für den Thread-Wechsel Wissen Sie, wann Sie es ausführen müssen? Tatsächlich generiert der Thread-Pool einige Hilfsthreads, um diese Auslösebedingungen zu überwachen. Sobald die Bedingungen erfüllt sind, nehmen diese Hilfsthreads selbst natürlich auch Zeit in Anspruch, aber wenn Sie mehr Wartezeiten benötigen Threads, verwenden Sie den Thread-Pool. Die Vorteile werden deutlicher. Siehe Beispiel unten:
static AutoResetEvent ev=new AutoResetEvent(false);
public static int Main(string[] args)
{ ThreadPool.RegisterWaitForSingleObject(
ev,
neuer WaitOrTimerCallback(WaitThreadFunc),
4,
2000,
false//Gibt an, dass der Timer jedes Mal zurückgesetzt wird, wenn der Wartevorgang abgeschlossen ist, bis er sich abmeldet und wartet.
);
ThreadPool.QueueUserWorkItem (neuer WaitCallback (ThreadFunc),8);
Thread.Sleep (10000);
0 zurückgeben;
}
public static void ThreadFunc(object b)
{ Console.WriteLine ("das Objekt ist {0}",b);
for(int i=0;i<2;i++)
{ Thread.Sleep (1000);
ev.Set();
}
}
public static void WaitThreadFunc(object b,bool t)
{ Console.WriteLine ("das Objekt ist {0},t ist {1}",b,t);
}
Das Ergebnis seiner Operation ist:
Das Objekt ist 8
Das Objekt ist 4, t ist falsch
Das Objekt ist 4, t ist falsch
Das Objekt ist 4,t ist wahr
Das Objekt ist 4,t ist wahr
Das Objekt ist 4,t ist wahr
Aus den obigen Ergebnissen können wir ersehen, dass der Thread ThreadFunc einmal und WaitThreadFunc fünfmal ausgeführt wurde. Wir können den Grund für den Start dieses Threads anhand des bool t-Parameters in WaitOrTimerCallback beurteilen: Wenn t falsch ist, bedeutet dies waitObject, andernfalls liegt es an einer Zeitüberschreitung. Darüber hinaus können wir auch einige Parameter über Objekt b an den Thread übergeben.
3. Timer
Es eignet sich für Methoden, die regelmäßig aufgerufen werden müssen. Es wird nicht in dem Thread ausgeführt, der den Timer erstellt hat. Es wird in einem separaten Thread ausgeführt, der vom System automatisch zugewiesen wird. Dies ähnelt der SetTimer-Methode in Win32. Seine Struktur ist:
öffentlicher Timer(
TimerCallback Rückruf,//Aufzurufende Methode
Objektstatus, // Parameter, die an den Rückruf übergeben werden
int dueTime,//Wie lange dauert es, bis der Rückruf beginnt?
int period//Das Zeitintervall für den Aufruf dieser Methode
); // Wenn dueTime 0 ist, führt Callback seinen ersten Aufruf sofort aus. Wenn dueTime Infinite ist, ruft der Rückruf seine Methode nicht auf. Der Timer ist deaktiviert, kann aber mit der Change-Methode wieder aktiviert werden. Wenn period 0 oder Infinite ist und dueTime nicht Infinite ist, ruft Callback seine Methode einmal auf. Das periodische Verhalten des Timers ist deaktiviert, kann jedoch mit der Change-Methode wieder aktiviert werden. Wenn period null (0) oder unendlich ist und dueTime nicht unendlich ist, ruft der Rückruf seine Methode einmal auf. Das periodische Verhalten des Timers ist deaktiviert, kann jedoch mit der Change-Methode wieder aktiviert werden.
Wenn wir den Zeitraum und die DueTime des Timers nach seiner Erstellung ändern möchten, können wir dies ändern, indem wir die Change-Methode von Timer aufrufen:
öffentlicher bool Change(
int dueTime,
int-Periode
);//Offensichtlich entsprechen die beiden geänderten Parameter den beiden Parametern im Timer
public static int Main(string[] args)
{
Console.WriteLine („Punkt ist 1000“);
Timer tm=new Timer (new TimerCallback (TimerCall),3,1000,1000);
Thread.Sleep (2000);
Console.WriteLine („Punkt ist 500“);
tm.Change (0,800);
Thread.Sleep (3000);
0 zurückgeben;
}
public static void TimerCall(object b)
{
Console.WriteLine ("timercallback; b ist {0}",b);
}
Das Ergebnis seiner Operation ist:
Periode ist 1000
timercallback;b ist 3
timercallback;b ist 3
Periode ist 500
timercallback;b ist 3
timercallback;b ist 3
timercallback;b ist 3
timercallback;b ist 3
Zusammenfassen
Aus der obigen kurzen Einführung können wir erkennen, in welchen Fällen sie verwendet werden: Thread eignet sich für Gelegenheiten, die eine komplexe Steuerung von Threads erfordern; ThreadPool eignet sich für kurze Aufgaben, die mehrere Threads erfordern (z. B. einige, die häufig blockiert werden). ); Timer eignet sich für Methoden, die regelmäßig aufgerufen werden müssen. Solange wir ihre Nutzungseigenschaften verstehen, können wir die geeignete Methode gut auswählen.