Procédure : Effectuer des appels thread-safe à des contrôles Windows FormsHow to: Make thread-safe calls to Windows Forms controls

Le multithreading peut améliorer les performances des applications de Windows Forms, mais l’accès aux contrôles de Windows Forms n’est pas thread-safe par nature.Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn't inherently thread-safe. Le multithreading peut exposer votre code à des bogues très sérieux et complexes.Multithreading can expose your code to very serious and complex bugs. Au moins deux threads manipulant un contrôle peuvent forcer le contrôle dans un état incohérent et provoquer des conditions de concurrence, des blocages, des blocages et des blocages.Two or more threads manipulating a control can force the control into an inconsistent state and lead to race conditions, deadlocks, and freezes or hangs. Si vous implémentez le multithreading dans votre application, veillez à appeler les contrôles inter-threads de façon thread-safe.If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. Pour plus d’informations, consultez meilleures pratiques pour le threading managé.For more information, see Managed threading best practices.

Il existe deux façons d’appeler sans risque un contrôle Windows Forms à partir d’un thread qui n’a pas créé ce contrôle.There are two ways to safely call a Windows Forms control from a thread that didn't create that control. Vous pouvez utiliser la System.Windows.Forms.Control.Invoke méthode pour appeler un délégué créé dans le thread principal, qui à son tour appelle le contrôle.You can use the System.Windows.Forms.Control.Invoke method to call a delegate created in the main thread, which in turn calls the control. Ou vous pouvez implémenter un System.ComponentModel.BackgroundWorker, qui utilise un modèle piloté par les événements pour séparer le travail effectué dans le thread d’arrière-plan de la création de rapports sur les résultats.Or, you can implement a System.ComponentModel.BackgroundWorker, which uses an event-driven model to separate work done in the background thread from reporting on the results.

Appels inter-threads non sécurisésUnsafe cross-thread calls

Il est risqué d’appeler un contrôle directement à partir d’un thread qui ne l’a pas créé.It's unsafe to call a control directly from a thread that didn't create it. L’extrait de code suivant illustre un appel non sécurisé au System.Windows.Forms.TextBox contrôle.The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. Le Button1_Click gestionnaire d’événements crée un WriteTextUnsafe nouveau thread, qui définit directement la propriété TextBox.Text du thread principal.The Button1_Click event handler creates a new WriteTextUnsafe thread, which sets the main thread's TextBox.Text property directly.

private void Button1_Click(object sender, EventArgs e)
{
    thread2 = new Thread(new ThreadStart(WriteTextUnsafe));
    thread2.Start();
}
private void WriteTextUnsafe()
{
    textBox1.Text = "This text was set unsafely.";
}
Private Sub Button1_Click(ByVal sender As Object, e As EventArgs) Handles Button1.Click
    Thread2 = New Thread(New ThreadStart(AddressOf WriteTextUnsafe))
    Thread2.Start()
End Sub

Private Sub WriteTextUnsafe()
    TextBox1.Text = "This text was set unsafely."
End Sub

Le débogueur Visual Studio détecte ces appels de threads non sécurisés en InvalidOperationException déclenchant un avec le message, opération inter-threads non valide. Contrôle «» accessible à partir d’un thread autre que le thread sur lequel il a été créé.The Visual Studio debugger detects these unsafe thread calls by raising an InvalidOperationException with the message, Cross-thread operation not valid. Control "" accessed from a thread other than the thread it was created on. Se InvalidOperationException produit toujours pour les appels inter-threads non sécurisés pendant le débogage de Visual Studio et peut se produire au moment de l’exécution de l’application.The InvalidOperationException always occurs for unsafe cross-thread calls during Visual Studio debugging, and may occur at app runtime. Vous devez résoudre le problème, mais vous pouvez désactiver l’exception en affectant Control.CheckForIllegalCrossThreadCalls à falsela propriété la valeur.You should fix the issue, but you can disable the exception by setting the Control.CheckForIllegalCrossThreadCalls property to false.

Appels inter-threads sécurisésSafe cross-thread calls

Les exemples de code suivants illustrent deux façons d’appeler sans risque un contrôle Windows Forms à partir d’un thread qui n’a pas créé ce dernier :The following code examples demonstrate two ways to safely call a Windows Forms control from a thread that didn't create it:

  1. La System.Windows.Forms.Control.Invoke méthode, qui appelle un délégué du thread principal pour appeler le contrôle.The System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. System.ComponentModel.BackgroundWorker Composant, qui offre un modèle piloté par les événements.A System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

Dans les deux exemples, le thread d’arrière-plan se met en veille pendant une seconde pour simuler le travail effectué dans ce thread.In both examples, the background thread sleeps for one second to simulate work being done in that thread.

Vous pouvez générer et exécuter ces exemples comme des applications .NET Framework à C# partir de la ligne de commande ou Visual Basic.You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. Pour plus d’informations, consultez génération à partir de la ligne de commande avec CSC. exe ou Build à partir de la ligne de commande (Visual Basic).For more information, see Command-line building with csc.exe or Build from the command line (Visual Basic).

À compter de .net Core 3,0, vous pouvez également générer et exécuter les exemples en tant qu’applications Windows .net core à partir d’un dossier qui a un <nom de dossier Windows Forms .net Core > fichier de projet. csproj.Starting with .NET Core 3.0, you can also build and run the examples as Windows .NET Core apps from a folder that has a .NET Core Windows Forms <folder name>.csproj project file.

Exemple : Utiliser la méthode Invoke avec un déléguéExample: Use the Invoke method with a delegate

L’exemple suivant illustre un modèle permettant de garantir des appels thread-safe à un contrôle Windows Forms.The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. Elle interroge la System.Windows.Forms.Control.InvokeRequired propriété, qui compare l’ID de thread de création du contrôle à l’ID de thread appelant.It queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control's creating thread ID to the calling thread ID. Si les ID de thread sont identiques, il appelle le contrôle directement.If the thread IDs are the same, it calls the control directly. Si les ID de thread sont différents, il appelle Control.Invoke la méthode avec un délégué du thread principal, qui effectue l’appel réel au contrôle.If the thread IDs are different, it calls the Control.Invoke method with a delegate from the main thread, which makes the actual call to the control.

Le SafeCallDelegate permet de définir TextBox la propriété Text du contrôle.The SafeCallDelegate enables setting the TextBox control's Text property. La WriteTextSafe méthode interroge InvokeRequired.The WriteTextSafe method queries InvokeRequired. Si InvokeRequired retourne true, passeWriteTextSafe àlaInvoke méthode pour effectuer l’appel réel au contrôle. SafeCallDelegateIf InvokeRequired returns true, WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. Si InvokeRequired retourne false, WriteTextSafe définit directement.TextBox.TextIf InvokeRequired returns false, WriteTextSafe sets the TextBox.Text directly. Le Button1_Click gestionnaire d’événements crée le nouveau thread et exécute WriteTextSafe la méthode.The Button1_Click event handler creates the new thread and runs the WriteTextSafe method.

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class InvokeThreadSafeForm : Form
{
    private delegate void SafeCallDelegate(string text);
    private Button button1;
    private TextBox textBox1;
    private Thread thread2 = null;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        Application.EnableVisualStyles();
        Application.Run(new InvokeThreadSafeForm());
    }
    public InvokeThreadSafeForm()
    {
        button1 = new Button
        {
            Location = new Point(15, 55),
            Size = new Size(240, 20),
            Text = "Set text safely"
        };
        button1.Click += new EventHandler(Button1_Click);
        textBox1 = new TextBox
        {
            Location = new Point(15, 15),
            Size = new Size(240, 20)
        };
        Controls.Add(button1);
        Controls.Add(textBox1);
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        thread2 = new Thread(new ThreadStart(SetText));
        thread2.Start();
        Thread.Sleep(1000);
    }

    private void WriteTextSafe(string text)
    {
        if (textBox1.InvokeRequired)
        {
            var d = new SafeCallDelegate(WriteTextSafe);
            textBox1.Invoke(d, new object[] { text });
        }
        else
        {
            textBox1.Text = text;
        }
    }

    private void SetText()
    {
        WriteTextSafe("This text was set safely.");
    }
}
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms

Public Class InvokeThreadSafeForm : Inherits Form

    Public Shared Sub Main()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.EnableVisualStyles()
        Dim frm As New InvokeThreadSafeForm()
        Application.Run(frm)
    End Sub

    Dim WithEvents Button1 As Button
    Dim TextBox1 As TextBox
    Dim Thread2 as Thread = Nothing

    Delegate Sub SafeCallDelegate(text As String)

    Private Sub New()
        Button1 = New Button()
        With Button1
            .Location = New Point(15, 55)
            .Size = New Size(240, 20)
            .Text = "Set text safely"
        End With
        TextBox1 = New TextBox()
        With TextBox1
            .Location = New Point(15, 15)
            .Size = New Size(240, 20)
        End With
        Controls.Add(Button1)
        Controls.Add(TextBox1)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Thread2 = New Thread(New ThreadStart(AddressOf SetText))
        Thread2.Start()
        Thread.Sleep(1000)
    End Sub

    Private Sub WriteTextSafe(text As String)
        If TextBox1.InvokeRequired Then
            Dim d As New SafeCallDelegate(AddressOf SetText)
            TextBox1.Invoke(d, New Object() {text})
        Else
            TextBox1.Text = text
        End If
    End Sub

    Private Sub SetText()
        WriteTextSafe("This text was set safely.")
    End Sub
End Class

Exemple : Utiliser un gestionnaire d’événements BackgroundWorkerExample: Use a BackgroundWorker event handler

Un moyen simple d’implémenter le multithreading consiste System.ComponentModel.BackgroundWorker à utiliser le composant, qui utilise un modèle piloté par les événements.An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. Le thread d’arrière- BackgroundWorker.DoWork plan exécute l’événement, qui n’interagit pas avec le thread principal.The background thread runs the BackgroundWorker.DoWork event, which doesn't interact with the main thread. Le thread principal exécute les BackgroundWorker.ProgressChanged gestionnaires BackgroundWorker.RunWorkerCompleted d’événements et, qui peuvent appeler les contrôles du thread principal.The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread's controls.

Pour effectuer un appel thread-safe à l' BackgroundWorkeraide de, créez une méthode dans le thread d’arrière-plan pour effectuer le travail et DoWork liez-la à l’événement.To make a thread-safe call by using BackgroundWorker, create a method in the background thread to do the work, and bind it to the DoWork event. Créez une autre méthode dans le thread principal pour signaler les résultats du travail en arrière-plan et liez- ProgressChanged le RunWorkerCompleted à l’événement ou.Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. Pour démarrer le thread d’arrière- BackgroundWorker.RunWorkerAsyncplan, appelez.To start the background thread, call BackgroundWorker.RunWorkerAsync.

L’exemple utilise le RunWorkerCompleted gestionnaire d’événements pour définir TextBox la propriété Text du contrôle.The example uses the RunWorkerCompleted event handler to set the TextBox control's Text property. Pour obtenir un exemple d' ProgressChanged utilisation de l' BackgroundWorkerévénement, consultez.For an example using the ProgressChanged event, see BackgroundWorker.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class BackgroundWorkerForm : Form
{
    private BackgroundWorker backgroundWorker1;
    private Button button1;
    private TextBox textBox1;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        Application.EnableVisualStyles();
        Application.Run(new BackgroundWorkerForm());
    }
    public BackgroundWorkerForm()
    {
        backgroundWorker1 = new BackgroundWorker();
        backgroundWorker1.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
        backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted);
        button1 = new Button
        {
            Location = new Point(15, 55),
            Size = new Size(240, 20),
            Text = "Set text safely with BackgroundWorker"
        };
        button1.Click += new EventHandler(Button1_Click);
        textBox1 = new TextBox
        {
            Location = new Point(15, 15),
            Size = new Size(240, 20)
        };
        Controls.Add(button1);
        Controls.Add(textBox1);
    }
    private void Button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }

    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // Sleep 2 seconds to emulate getting data.
        Thread.Sleep(2000);
        e.Result = "This text was set safely by BackgroundWorker.";
    }

    private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        textBox1.Text = e.Result.ToString();
    }
}
Imports System.ComponentModel
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms

Public Class BackgroundWorkerForm : Inherits Form

    Public Shared Sub Main()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.EnableVisualStyles()
        Dim frm As New BackgroundWorkerForm()
        Application.Run(frm)
    End Sub

    Dim WithEvents BackgroundWorker1 As BackgroundWorker
    Dim WithEvents Button1 As Button
    Dim TextBox1 As TextBox

    Private Sub New()
        BackgroundWorker1 = New BackgroundWorker()
        Button1 = New Button()
        With Button1
            .Text = "Set text safely with BackgroundWorker"
            .Location = New Point(15, 55)
            .Size = New Size(240, 20)
        End With
        TextBox1 = New TextBox()
        With TextBox1
            .Location = New Point(15, 15)
            .Size = New Size(240, 20)
        End With
        Controls.Add(Button1)
        Controls.Add(TextBox1)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) _
     Handles BackgroundWorker1.DoWork
        ' Sleep 2 seconds to emulate getting data.
        Thread.Sleep(2000)
        e.Result = "This text was set safely by BackgroundWorker."
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) _
     Handles BackgroundWorker1.RunWorkerCompleted
        textBox1.Text = e.Result.ToString()
    End Sub
End Class

Voir aussiSee also