Postup volání ovládacích prvků bezpečných pro přístup z více vláken (model Windows Forms .NET)
Multithreading může zlepšit výkon model Windows Forms aplikací, ale přístup k ovládacím prvkům model Windows Forms není ze své podstaty bezpečný pro přístup z více vláken. Multithreading může váš kód vystavit vážným a složitým chybám. Dvě nebo více vláken manipulující s ovládacím prvek může vynutit řízení do nekonzistentního stavu a vést k podmínkám časování, zablokování a zablokování nebo zablokování. Pokud ve své aplikaci implementujete vícevláknové funkce, nezapomeňte volat ovládací prvky napříč vlákny bezpečným způsobem. Další informace najdete v tématu Osvědčené postupy spravovaných vláken.
Důležité
Dokumentace k desktopové příručce pro .NET 7 a .NET 6 se právě připravuje.
Existují dva způsoby, jak bezpečně volat ovládací prvek model Windows Forms z vlákna, které ovládací prvek nevytvořil. System.Windows.Forms.Control.Invoke Metoda slouží k volání delegáta vytvořeného v hlavním vlákně, který pak volá ovládací prvek. Nebo implementujte System.ComponentModel.BackgroundWorkermodel řízený událostmi, který používá model řízený událostmi k oddělení práce provedené ve vlákně na pozadí od generování sestav výsledků.
Nebezpečná volání mezi vlákny
Volání ovládacího prvku přímo z vlákna, které ho nevytvořilo, je nebezpečné. Následující fragment kódu znázorňuje nebezpečné volání System.Windows.Forms.TextBox ovládacího prvku. Obslužná rutina Button1_Click
události vytvoří nové WriteTextUnsafe
vlákno, které nastaví vlastnost hlavního TextBox.Text vlákna přímo.
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
Ladicí program sady Visual Studio detekuje tato nebezpečná volání vlákna vyvoláním InvalidOperationException operace křížového vlákna, která není platná. Řízení přístupu z jiného vlákna, než je vlákno, ve které bylo vytvořeno. Vždy InvalidOperationException dochází k nebezpečným voláním mezi vlákny během ladění sady Visual Studio a může dojít za běhu aplikace. Problém byste měli vyřešit, ale výjimku můžete zakázat nastavením Control.CheckForIllegalCrossThreadCalls vlastnosti na false
hodnotu .
Sejf volání mezi vlákny
Následující příklady kódu ukazují dva způsoby bezpečného volání ovládacího prvku model Windows Forms z vlákna, které ho nevytvořilo:
- Metoda System.Windows.Forms.Control.Invoke , která volá delegáta z hlavního vlákna pro volání ovládacího prvku.
- Komponenta System.ComponentModel.BackgroundWorker , která nabízí model řízený událostmi.
V oboupříkladch
Příklad: Použití metody Invoke
Následující příklad ukazuje vzor pro zajištění volání typu thread-safe do model Windows Forms řízení. Dotazuje se na System.Windows.Forms.Control.InvokeRequired vlastnost, která porovnává ID vlákna vytvářeného ovládacího prvku s ID volajícího vlákna. Pokud se liší, měli byste metodu Control.Invoke volat.
Umožňuje WriteTextSafe
nastavit TextBox vlastnost ovládacího prvku Text na novou hodnotu. Metoda dotazuje InvokeRequired. Pokud InvokeRequired vrátí true
, WriteTextSafe
rekurzivně volá sám sebe, předání metody jako delegát metody Invoke . Pokud InvokeRequired se vrátí false
, WriteTextSafe
nastaví přímo TextBox.Text . Obslužná rutina Button1_Click
události vytvoří nové vlákno a spustí metodu WriteTextSafe
.
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
Příklad: Použití BackgroundWorkeru
Snadný způsob implementace multithreadingu je s komponentou System.ComponentModel.BackgroundWorker , která používá model řízený událostmi. Vlákno na pozadí vyvolá BackgroundWorker.DoWork událost, která nepracuje s hlavním vláknem. Hlavní vlákno spouští BackgroundWorker.ProgressChanged obslužné rutiny událostí BackgroundWorker.RunWorkerCompleted , které mohou volat ovládací prvky hlavního vlákna.
Pokud chcete provést volání bezpečné pro přístup z více vláken, BackgroundWorkerzpracujte DoWork událost. Existují dvě události, které pracovní proces na pozadí používá k hlášení stavu: ProgressChanged a RunWorkerCompleted. Událost ProgressChanged
se používá ke komunikaci aktualizací stavu do hlavního vlákna a RunWorkerCompleted
událost se používá k signálu, že pracovní proces na pozadí dokončil svou práci. Pokud chcete spustit vlákno na pozadí, zavolejte BackgroundWorker.RunWorkerAsync.
Příklad počítá v události od 0 do 10 DoWork
a mezi počty pozastaví jednu sekundu. Pomocí obslužné rutiny ProgressChanged události hlásí číslo zpět do hlavního vlákna a nastaví TextBox vlastnost ovládacího prvku Text . ProgressChanged Aby událost fungovalaBackgroundWorker.WorkerReportsProgress, musí být vlastnost nastavena na true
hodnotu .
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
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro