Sdílet prostřednictvím


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 falsehodnotu .

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:

  1. Metoda System.Windows.Forms.Control.Invoke , která volá delegáta z hlavního vlákna pro volání ovládacího prvku.
  2. 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 truehodnotu .

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