Comment effectuer des appels thread-safe aux contrôles (Windows Forms .NET)
Le multithreading peut améliorer les performances des applications Windows Forms, mais l’accès aux contrôles Windows Forms n’est pas intrinsèquement thread-safe. Le multithreading peut exposer votre code à des bogues sérieux et complexes. Deux threads ou plus qui manipulent un contrôle peuvent forcer le contrôle dans un état incohérent et entraîner des conditions de concurrence, des interblocages et des blocages ou des blocages. Si vous implémentez le multithreading dans votre application, veillez à appeler des contrôles interthread de manière sécurisée. Pour plus d’informations, consultez les meilleures pratiques en matière de threads managés.
Important
La documentation du Guide du bureau pour .NET 7 et .NET 6 est en cours de construction.
Il existe deux façons d’appeler en toute sécurité un contrôle Windows Forms à partir d’un thread qui n’a pas créé ce contrôle. Utilisez la System.Windows.Forms.Control.Invoke méthode pour appeler un délégué créé dans le thread principal, qui appelle à son tour le contrôle. Ou, implémentez un System.ComponentModel.BackgroundWorkermodè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.
Appels interthread non sécurisés
Il est dangereux d’appeler un contrôle directement à partir d’un thread qui ne l’a pas créé. L’extrait de code suivant illustre un appel non sécurisé au System.Windows.Forms.TextBox contrôle. Le Button1_Click
gestionnaire d’événements crée un thread WriteTextUnsafe
, qui définit directement la propriété du TextBox.Text thread principal.
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
Le débogueur Visual Studio détecte ces appels de thread non sécurisés en générant un InvalidOperationException message, l’opération interthread n’est pas valide. Contrôle accessible à partir d’un thread autre que le thread sur lequel il a été créé. Le InvalidOperationException problème se produit toujours pour les appels interthread non sécurisés pendant le débogage de Visual Studio et peut se produire au moment de l’exécution de l’application. Vous devez résoudre le problème, mais vous pouvez désactiver l’exception en définissant la Control.CheckForIllegalCrossThreadCalls propriété false
sur .
Coffre appels interthreads
Les exemples de code suivants montrent deux façons d’appeler en toute sécurité un contrôle Windows Forms à partir d’un thread qui ne l’a pas créé :
- Méthode System.Windows.Forms.Control.Invoke , qui appelle un délégué du thread principal pour appeler le contrôle.
- Composant System.ComponentModel.BackgroundWorker , qui offre un modèle piloté par les événements.
Dans les deux exemples, le thread d’arrière-plan veille pendant une seconde pour simuler le travail effectué dans ce thread.
Exemple : Utiliser la méthode Invoke
L’exemple suivant illustre un modèle permettant de garantir des appels thread-safe à un contrôle Windows Forms. Il 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. S’ils sont différents, vous devez appeler la Control.Invoke méthode.
Permet WriteTextSafe
de définir la TextBox propriété du Text contrôle sur une nouvelle valeur. Les requêtes InvokeRequiredde méthode . Si InvokeRequired la valeur est retournée true
, WriteTextSafe
elle s’appelle de manière récursive, en passant la méthode en tant que délégué à la Invoke méthode. Si InvokeRequired cette propriété est retournée false
, WriteTextSafe
définit la TextBox.Text valeur directement. Le Button1_Click
gestionnaire d’événements crée le nouveau thread et exécute la WriteTextSafe
méthode.
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
Exemple : Utiliser un BackgroundWorker
Un moyen simple d’implémenter le multithreading est avec le System.ComponentModel.BackgroundWorker composant, qui utilise un modèle piloté par les événements. Le thread d’arrière-plan déclenche l’événement BackgroundWorker.DoWork , qui n’interagit pas avec le thread principal. Le thread principal exécute les BackgroundWorker.ProgressChanged gestionnaires d’événements et BackgroundWorker.RunWorkerCompleted les gestionnaires d’événements, qui peuvent appeler les contrôles du thread principal.
Pour effectuer un appel thread-safe à l’aide BackgroundWorkerde , gérez l’événement DoWork . Il existe deux événements que le worker en arrière-plan utilise pour signaler l’état : ProgressChanged et RunWorkerCompleted. L’événement ProgressChanged
est utilisé pour communiquer les mises à jour d’état au thread principal, et l’événement RunWorkerCompleted
est utilisé pour signaler que le worker en arrière-plan a terminé son travail. Pour démarrer le thread d’arrière-plan, appelez BackgroundWorker.RunWorkerAsync.
L’exemple compte de 0 à 10 dans l’événement DoWork
, ce qui s’interrompt pour une seconde entre les nombres. Il utilise le ProgressChanged gestionnaire d’événements pour signaler le nombre au thread principal et définir la TextBox propriété du Text contrôle. Pour que l’événement ProgressChanged fonctionne, la BackgroundWorker.WorkerReportsProgress propriété doit être définie sur true
.
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
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour