Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Multithreading kann die Leistung von Windows Forms-Apps verbessern, aber der Zugriff auf Windows Forms-Steuerelemente ist nicht von Natur aus threadsicher. Multithreading kann Ihren Code schwerwiegenden und komplexen Fehlern aussetzen. Wenn zwei oder mehr Threads ein Steuerelement ändern, wird das Steuerelement möglicherweise in einen inkonsistenten Zustand versetzt, und es kann zu Racebedingungen, Deadlocks, zum Einfrieren und zu Fehlern kommen. Wenn Sie Multithreading in Ihrer App implementieren, achten Sie darauf, Threadübergreifende Steuerelemente auf threadsichere Weise aufzurufen. Weitere Informationen finden Sie unter Bewährte Methoden für verwaltetes Threading.
Es gibt zwei Möglichkeiten, ein Windows Forms-Steuerelement aus einem Thread, der dieses Steuerelement nicht erstellt hat, sicher aufzurufen. Verwenden Sie die System.Windows.Forms.Control.Invoke Methode, um einen im Hauptthread erstellten Delegaten aufzurufen, der wiederum das Steuerelement aufruft. Oder implementieren Sie ein System.ComponentModel.BackgroundWorker, das ein ereignisgesteuertes Modell verwendet, um die im Hintergrundthread erledigte Arbeit von der Berichterstellung zu den Ergebnissen zu trennen.
Unsichere threadübergreifende Aufrufe
Es ist unsicher, ein Steuerelement direkt aus einem Thread aufzurufen, der es nicht erstellt hat. Der folgende Codeausschnitt veranschaulicht einen unsicheren Aufruf des System.Windows.Forms.TextBox-Steuerelements. Der Button1_Click
-Ereignishandler erstellt einen neuen WriteTextUnsafe
-Thread, der die TextBox.Text-Eigenschaft des Hauptthreads direkt festlegt.
private void button1_Click(object sender, EventArgs e)
{
var thread2 = new System.Threading.Thread(WriteTextUnsafe);
thread2.Start();
}
private void WriteTextUnsafe() =>
textBox1.Text = "This text was set unsafely.";
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim thread2 As New System.Threading.Thread(AddressOf WriteTextUnsafe)
thread2.Start()
End Sub
Private Sub WriteTextUnsafe()
TextBox1.Text = "This text was set unsafely."
End Sub
Der Visual Studio-Debugger erkennt diese unsicheren Threadaufrufe, indem er eine InvalidOperationException mit der Meldung auslöst: Threadübergreifende Operationen sind ungültig. Das Steuerelement wurde von einem anderen Thread aus angesprochen als dem, auf dem es erstellt wurde. Diese InvalidOperationException tritt immer bei unsicheren threadübergreifenden Aufrufen während des Visual Studio-Debuggings auf und kann auch zur Laufzeit der App auftreten. Sie sollten das Problem beheben, aber Sie können die Ausnahme deaktivieren, indem Sie die eigenschaft Control.CheckForIllegalCrossThreadCalls auf false
festlegen.
Sichere threadübergreifende Aufrufe
Die folgenden Codebeispiele veranschaulichen zwei Möglichkeiten zum sicheren Aufrufen eines Windows Forms-Steuerelements aus einem Thread, der es nicht erstellt hat:
- Die System.Windows.Forms.Control.Invoke-Methode, die einen Delegaten aus dem Hauptthread aufruft, um das Steuerelement aufzurufen
- Eine System.ComponentModel.BackgroundWorker-Komponente, die ein ereignisgesteuertes Modell bietet.
In beiden Beispielen wartet der Hintergrundthread eine Sekunde, um die in diesem Thread ausgeführten Vorgänge zu simulieren.
Beispiel: Verwenden der Invoke-Methode
Im folgenden Beispiel wird ein Muster gezeigt, mit dem Sie threadsichere Aufrufe eines Windows Forms-Steuerelements sicherstellen können. Dabei wird die System.Windows.Forms.Control.InvokeRequired-Eigenschaft abgefragt, und die ID des Erstellungsthreads für das Steuerelement wird mit der ID des aufrufenden Threads verglichen. Wenn sie anders sind, sollten Sie die Control.Invoke Methode aufrufen.
Dies WriteTextSafe
ermöglicht das Festlegen der Eigenschaft des TextBox Steuerelements Text auf einen neuen Wert. Die Methode fragt InvokeRequired ab. Wenn InvokeRequiredtrue
zurückkehrt, ruft WriteTextSafe
sich rekursiv selbst auf und übergibt sie als Delegat an die Invoke Methode. Wenn InvokeRequiredfalse
zurückgibt, legt WriteTextSafe
die TextBox.Text direkt fest. Der Button1_Click
-Ereignishandler erstellt den neuen Thread und führt die WriteTextSafe
-Methode aus.
private void button1_Click(object sender, EventArgs e)
{
var threadParameters = new System.Threading.ThreadStart(delegate { WriteTextSafe("This text was set safely."); });
var thread2 = new System.Threading.Thread(threadParameters);
thread2.Start();
}
public void WriteTextSafe(string text)
{
if (textBox1.InvokeRequired)
{
// Call this same method but append THREAD2 to the text
Action safeWrite = delegate { WriteTextSafe($"{text} (THREAD2)"); };
textBox1.Invoke(safeWrite);
}
else
textBox1.Text = text;
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim threadParameters As New System.Threading.ThreadStart(Sub()
WriteTextSafe("This text was set safely.")
End Sub)
Dim thread2 As New System.Threading.Thread(threadParameters)
thread2.Start()
End Sub
Private Sub WriteTextSafe(text As String)
If (TextBox1.InvokeRequired) Then
TextBox1.Invoke(Sub()
WriteTextSafe($"{text} (THREAD2)")
End Sub)
Else
TextBox1.Text = text
End If
End Sub
Beispiel: Verwenden eines BackgroundWorkers
Eine einfache Möglichkeit zum Implementieren von Multithreading ist die System.ComponentModel.BackgroundWorker Komponente, die ein ereignisgesteuertes Modell verwendet. Der Hintergrundthread löst das BackgroundWorker.DoWork Ereignis aus, das nicht mit dem Hauptthread interagiert. Der Hauptthread führt die Ereignishandler BackgroundWorker.ProgressChanged und BackgroundWorker.RunWorkerCompleted aus, die die Steuerelemente des Hauptthreads aufrufen können.
Behandeln Sie das BackgroundWorker-Ereignis, um einen threadsicheren Aufruf mit DoWork zu machen. Es gibt zwei Ereignisse, die der Hintergrundmitarbeiter zum Melden des Status verwendet: ProgressChanged und RunWorkerCompleted. Das ProgressChanged
Ereignis wird verwendet, um Statusaktualisierungen mit dem Hauptthread zu kommunizieren, und das RunWorkerCompleted
Ereignis wird verwendet, um zu signalisieren, dass der Hintergrundmitarbeiter seine Arbeit abgeschlossen hat. Rufen Sie BackgroundWorker.RunWorkerAsyncauf, um den Hintergrundthread zu starten.
Das Beispiel zählt von 0 bis 10 im DoWork
Ereignis, wobei nach jedem Zählen eine Sekunde pausiert wird. Er verwendet den ProgressChanged Ereignishandler, um die Nummer zurück an den Hauptthread zu melden und die TextBox Eigenschaft des Steuerelements Text festzulegen. Damit das ProgressChanged Ereignis funktioniert, muss die BackgroundWorker.WorkerReportsProgress Eigenschaft auf true
gesetzt werden.
private void button1_Click(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int counter = 0;
int max = 10;
while (counter <= max)
{
backgroundWorker1.ReportProgress(0, counter.ToString());
System.Threading.Thread.Sleep(1000);
counter++;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) =>
textBox1.Text = (string)e.UserState;
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If (Not BackgroundWorker1.IsBusy) Then
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim counter = 0
Dim max = 10
While counter <= max
BackgroundWorker1.ReportProgress(0, counter.ToString())
System.Threading.Thread.Sleep(1000)
counter += 1
End While
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
TextBox1.Text = e.UserState
End Sub
.NET Desktop feedback