Comment puis-je mettre à jour l'interface graphique à partir d'un autre thread?
Quelle est la façon la plus simple de mettre à jour un Label
partir d'un autre thread?
J'ai un Form
sur thread1
, et depuis que je suis à partir d'un autre thread (thread2
). Alors que thread2
est du traitement de certains fichiers que je voudrais mettre à jour un Label
sur le Form
avec l'état actuel de thread2
's de travail.
Comment puis-je le faire?
- Ne fait pas .net 2.0+ ont la classe BackgroundWorker juste pour cela. Elle thread d'INTERFACE utilisateur courant. 1. Créer un BackgroundWorker 2. Ajouter deux délégués (un pour le traitement, et l'autre pour l'achèvement des travaux)
- peut-être un peu en retard : codeproject.com/KB/cs/Threadsafe_formupdating.aspx
- Voir la réponse .NET 4.5, C# 5.0: stackoverflow.com/a/18033198/2042090
- Cette question ne s'applique pas à Gtk# GUI. Pour Gtk# voir ceci et ceci la réponse.
- Attention: les réponses à cette question sont désormais encombré de gâchis de l'OT ("voici ce que j'ai fait pour mon application WPF") et de l'histoire .NET 2.0 artefacts.
Vous devez vous connecter pour publier un commentaire.
Pour .NET 2.0, voici un beau morceau de code que j'ai écrit qui fait exactement ce que vous voulez, et fonctionne bien sur un
Control
:L'appeler comme ceci:
Si vous êtes en utilisant .NET 3.0 ou supérieur, vous pouvez réécrire la méthode ci-dessus comme une extension de la méthode de la
Control
classe, qui serait alors de simplifier l'appel à:Mise à JOUR 05/10/2010:
Pour .NET 3.0, vous devez utiliser ce code:
qui utilise LINQ et les expressions lambda pour permettre à beaucoup plus propre, plus simple et plus sûr de la syntaxe:
N'est pas seulement le nom de la propriété à présent vérifié au moment de la compilation, le type de la propriété est ainsi, il est donc impossible de (par exemple) assigner une chaîne de valeur à une propriété booléenne, et donc la cause d'une exception d'exécution.
Malheureusement, cela n'a pas empêcher personne de faire des choses stupides comme en passant dans un autre
Control
's de la propriété et de la valeur, donc la suite sera heureux de compiler:Donc j'ai ajouté de l'exécution des vérifications pour s'assurer que la propriété n'appartiennent en fait à la
Control
que la méthode est appelée. Pas parfait, mais encore beaucoup mieux que la .NET version 2.0.Si quelqu'un a d'autres suggestions sur la façon d'améliorer ce code au moment de la compilation de la sécurité, s'il vous plaît commentaire!
SetControlPropertyThreadSafe(myLabel, "Text", status)
être appelé à partir d'un autre module ou de la classe ou de la formenew object[] { }
expressions. Pourquoi ne pas simplementnew[] { }
et de permettre au compilateur de déduire le type..nice bit of code
pour moiDatagridview.Datasource
?La plus simple est une méthode anonyme passé dans
Label.Invoke
:Avis que
Invoke
blocs d'exécution jusqu'à la fin de--c'est du code synchrone. La question à ne pas poser des questions sur le code asynchrone, mais il y a beaucoup de contenu sur un Débordement de Pile sur l'écriture de code asynchrone lorsque vous voulez apprendre à ce sujet.String validated = wsPr.validateBooking(iberostarbck.client.utils.IdConexion, dsNewBooking, ref errorTxt);
qui est un appel à un webservice avec parms (Décimal, DataSet, réf. de Chaîne). Des idées?Manipulation long travail
Depuis .NET 4.5, C# 5.0 vous devez utiliser Tâche Asynchrone basé sur le Modèle (TAP) avec async-attendre mots-clés dans tous les domaines (y compris l'interface graphique):
au lieu de Modèle de Programmation asynchrone (APM) et Basé sur des événements du Modèle Asynchrone (EAP) (celle-ci inclut le Classe BackgroundWorker).
Ensuite, la solution recommandée pour le nouveau développement est:
Asynchrone de la mise en œuvre d'un gestionnaire d'évènement (Oui, c'est tout):
Mise en œuvre de la deuxième thread qui avertit le thread d'INTERFACE utilisateur:
Avis la suivante:
Pour une plus détaillé des exemples, voir: L'Avenir de C#: les Bonnes choses viennent à ceux qui attendent' par Joseph Albahari.
Voir aussi à propos de Modèle de thread de l'INTERFACE utilisateur concept.
La gestion des exceptions
L'extrait de code ci-dessous est un exemple de la façon de gérer les exceptions et le bouton à bascule du
Enabled
de propriété pour empêcher plusieurs clics cours d'exécution en arrière-plan.SecondThreadConcern.LongWork()
déclenche une exception, peut-il être attrapé par le thread d'INTERFACE utilisateur? C'est un excellent post, btw.Invoke
répond à vos besoins et à votre base de code est petit, alors vous pouvez violer SoC sans perte perceptible à la faveur de la productivité et de facilité. Puis, lorsque votre code source devient plus grand et plus difficile à maintenir, vous pouvez toujours refactoriser le code à introduire la SoC (en supposant que la fiabilité des tests de régression).var progress = new Progress<string>(s => button.Text = s);
Task.Delay(500).Wait()
? Quel est le point de créer une Tâche de simplement bloquer le thread en cours? Vous ne devriez jamais bloquer un thread du pool!Variation de Marc Gravel est simple la solution pour .NET 4:
Ou utiliser l'Action de délégué à la place:
Voir ici pour une comparaison des deux: MethodInvoker vs d'Action pour le Contrôle.BeginInvoke
this.refresh()
à force d'invalider et de repeindre le GUI .. si c'est utile ..Feu et oublier de la méthode d'extension pour .NET 3.5+
Cela peut être appelée à l'aide de la ligne de code suivante:
@this
est tout simplement le nom de la variable, dans ce cas, la référence pour le contrôle du courant d'appel de l'extension. Vous pouvez la renommer la source, ou quelle que soit la flotte votre bateau. J'utilise@this
, parce que c'est en se référant à "ce Contrôle" qui est l'appel de l'extension et de l'est cohérent (dans ma tête, au moins) avec l'aide de la " ce mot-clé dans la normale (non-extension) du code.OnUIThread
plutôt queUIThread
.OnXXX
style est parce que cela indique généralement un événement de surcharge.OnLoad
,OnInitialize
, etc. Peut-être pas la meilleure décision, mais c'est ce que j'avais en tête à l'époque. Et wow, c'était presque 6 ans maintenant. Le temps passe vite.RunOnUiThread
. Mais c'est juste le goût personnel.C'est la façon classique de faire ceci:
Votre thread de travail est un événement. Votre thread d'INTERFACE utilisateur commence un autre thread pour faire le travail et les crochets jusqu'à ce travailleur de l'événement de sorte que vous pouvez afficher l'état du thread de travail.
Ensuite dans l'INTERFACE utilisateur, vous devez traverser les threads de modifier le contrôle effectif... comme une étiquette ou une barre de progression.
La solution la plus simple est d'utiliser
Control.Invoke
.Filetage code est souvent buggy et toujours difficile à tester. Vous n'avez pas besoin d'écrire de filetage code de mise à jour de l'interface utilisateur à partir d'une tâche en arrière-plan. Utilisez simplement le BackgroundWorker classe d'exécution de la tâche et de ses ReportProgress méthode pour mettre à jour l'interface utilisateur. Habituellement, vous avez juste rapport du pourcentage d'achèvement, mais il y a une autre surcharge qui comprend un état de l'objet. Voici un exemple que les rapports simplement un objet de type string:
C'est très bien si vous voulez toujours mettre à jour le même champ. Si vous avez plus compliqué mises à jour à faire, vous pouvez définir une classe pour représenter l'INTERFACE utilisateur de l'état et de le passer à la ReportProgress méthode.
Une dernière chose, assurez-vous de définir le
WorkerReportsProgress
drapeau, ou leReportProgress
méthode sera complètement ignoré.backgroundWorker1_RunWorkerCompleted
.La grande majorité des réponses utilisez
Control.Invoke
qui est un condition de course en attente de se produire. Considérons, par exemple, de la accepté de répondre:Si l'utilisateur ferme le formulaire juste avant
this.Invoke
est appelé (rappelez-vous,this
est leForm
objet), unObjectDisposedException
sera probablement tiré.La solution est d'utiliser
SynchronizationContext
, spécifiquementSynchronizationContext.Current
comme hamilton.danielb suggère (d'autres réponses reposent sur desSynchronizationContext
des implémentations qui est complètement inutile). J'ai légèrement modifier son code pour utiliserSynchronizationContext.Post
plutôt queSynchronizationContext.Send
si (comme il n'y a généralement pas besoin de le thread de travail à attendre):Noter que sur .NET 4.0 et vous devez vraiment être à l'aide de tâches pour des opérations asynchrones. Voir n-san réponse pour l'équivalent approche basée sur les tâches (à l'aide de
TaskScheduler.FromCurrentSynchronizationContext
).Enfin, sur .NET 4.5 et vous pouvez également utiliser
Progress<T>
(qui, en gros, captureSynchronizationContext.Current
, lors de sa création), comme démontré par Ryszard Dżegan de l' pour les cas où l'opération de longue durée doit exécuter le code de l'INTERFACE utilisateur tout en travaillant.Vous aurez à vous assurer que la mise à jour arrive sur le thread approprié; le thread d'INTERFACE utilisateur.
Pour ce faire, vous aurez pour Appeler le gestionnaire d'événements au lieu de l'appeler directement.
Vous pouvez le faire en augmentant votre événement comme celui-ci:
(Le code est tapé ici hors de ma tête, donc je n'ai pas vérifié pour la syntaxe correcte, etc., mais il devrait vous permettre de continuer.)
Noter que le code ci-dessus ne fonctionnera pas sur les projets WPF, depuis des contrôles WPF ne pas mettre en œuvre la
ISynchronizeInvoke
interface.Afin de s'assurer que le code ci-dessus fonctionne avec Windows Forms et WPF, et toutes les autres plates-formes, vous pouvez jeter un oeil à la
AsyncOperation
,AsyncOperationManager
etSynchronizationContext
classes.Afin de facilement déclencher des événements de cette façon, j'ai créé une méthode d'extension, ce qui me permet de simplifier déclenchant un événement simplement en appelant le:
Bien sûr, vous pouvez également faire usage de la classe BackGroundWorker, qui utilisera cette question pour vous.
Vous aurez besoin d'Invoquer la méthode sur le thread GUI. Vous pouvez le faire en appelant le Contrôle.Invoquer.
Par exemple:
En raison de la trivialité de la scénario, je voudrais en fait avoir le thread d'INTERFACE utilisateur sondage pour l'état. Je pense que vous trouverez qu'il peut être très élégant.
L'approche permet d'éviter le regroupement de fonctionnement requis lors de l'utilisation de la
ISynchronizeInvoke.Invoke
etISynchronizeInvoke.BeginInvoke
méthodes. Il n'y a rien de mal à utiliser le marshaling technique, mais il ya un couple de mises en garde, vous devez être conscient de.BeginInvoke
trop fréquemment ou peut-dépassement de capacité de la pompe de message.Invoke
sur le thread de travail est un appel bloquant. Il sera temporairement interrompre le travail accompli dans ce thread.La stratégie que je propose dans cette réponse, renverse les rôles de la communication des threads. Au lieu de le thread de pousser les données le thread d'INTERFACE utilisateur sondages pour elle. Ce modèle commun utilisé dans de nombreux scénarios. Car tous vous êtes désireux de le faire, c'est afficher les informations sur l'avancement de la thread de travail alors je pense que vous trouverez que cette solution est une excellente alternative pour le regroupement une solution. Il a les avantages suivants.
Control.Invoke
ouControl.BeginInvoke
approche étroitement les couples eux.Elapsed
cas, vous utilisez une méthode de membre de sorte que vous pouvez supprimer la minuterie lorsque le formulaire est disposé...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
et l'affectation viam_Timer.Elapsed += handler;
, plus tard dans la dispose contexte de faire unm_Timer.Elapsed -= handler;
suis-je droit? Et pour l'élimination/de clôture suivant les conseils que discuté ici.Aucun Invoquer des trucs dans la réponse à la question précédente est nécessaire.
Vous avez besoin de regarder WindowsFormsSynchronizationContext:
Celui-ci est similaire à la solution au-dessus de l'aide .NET Framework 3.0, mais il a résolu le problème de au moment de la compilation soutien à la sécurité.
À utiliser:
Le compilateur va échouer si l'utilisateur passe le mauvais type de données.
Salvete! Après avoir cherché pour cette question, j'ai trouvé les réponses par FrankG et Oregon Fantôme être le plus facile plus utile pour moi. Maintenant, je code en Visual Basic et a couru cet extrait par l'intermédiaire d'un convertisseur; donc je ne sais pas bien comment il se trouve.
J'ai une boîte de dialogue formulaire appelé
form_Diagnostics,
qui a un richtext boîte, appeléupdateDiagWindow,
qui je suis en utilisant comme une sorte de journalisation de l'écran. J'avais besoin d'être en mesure de mettre à jour son texte à partir de tous les threads. Les lignes supplémentaires permettent à la fenêtre pour faire défiler automatiquement les plus récentes lignes.Et donc, je peux maintenant mettre à jour l'affichage avec une ligne, de n'importe où dans l'ensemble du programme de la manière dont vous pensez qu'il serait de travailler sans aucun filetage:
Code principal (mettre cela à l'intérieur de votre formulaire de code de classe):
Pour de nombreuses raisons, il est aussi simple que cela:
"serviceGUI()" est une interface graphique, méthode de niveau à l'intérieur de la forme (ce) qui peut changer autant de commandes que vous le souhaitez. Appel "updateGUI()" de l'autre thread. Les paramètres peuvent être ajoutés à transmettre des valeurs, ou (probablement plus rapide) utiliser la classe portée des variables avec des serrures sur eux si il y a possibilité d'un affrontement entre les threads d'accéder à ce qui pourrait causer de l'instabilité. Utiliser BeginInvoke au lieu de l'Invoquer en cas de non-thread GUI est temps critique (en gardant Brian de Gédéon avertissement à l'esprit).
Dans mon C# 3.0 variation de Ian Kemp solution:
Vous l'appeler comme ceci:
Sinon, l'original est une très belle solution.
Noter que
BeginInvoke()
est préféré auInvoke()
parce qu'il est moins susceptible de provoquer des blocages (cependant, ce n'est pas un problème ici, quand il suffit de l'attribution du texte à l'étiquette):Lors de l'utilisation de
Invoke()
vous êtes en attente pour la méthode de retour. Maintenant, il se peut que vous faire quelque chose dans le invoquée code qui devront attendre le thread, ce qui peut ne pas être immédiatement évident si il est enterré dans certaines fonctions que vous appelez, qui lui-même peut se produire indirectement via des gestionnaires d'événements. Alors vous pourriez être en attente pour le fil, le fil serait en attente pour vous et vous êtes dans l'impasse.Ce fait causé certains de nos publié des logiciels pour bloquer. Il a été assez facile à corriger en remplaçant
Invoke()
avecBeginInvoke()
. Sauf si vous avez un besoin pour un fonctionnement synchrone, ce qui peut être le cas si vous avez besoin d'une valeur de retour, utilisezBeginInvoke()
.Quand j'ai rencontré le même problème j'ai demandé l'aide de Google, mais plutôt que de me donner une solution simple il me troublait de plus en donnant des exemples de
MethodInvoker
et bla bla bla. J'ai donc décidé de le résoudre sur mon propre. Voici ma solution:Faire un délégué comme ceci:
Vous pouvez appeler cette fonction dans un nouveau thread comme celui-ci
Ne pas confondre avec
Thread(() => .....)
. J'utilise une fonction anonyme ou une expression lambda, quand je travaille sur un fil. Afin de réduire les lignes de code, vous pouvez utiliser leThreadStart(..)
méthode que je ne suis pas censé expliquer ici.Simplement utiliser quelque chose comme ceci:
e.ProgressPercentage
, n'êtes-vous pas déjà dans le thread de l'INTERFACE utilisateur de la méthode que vous appelez cela?e
variable ? pas de code completVous pouvez utiliser le délégué
Action
:Ma version est à insérer une ligne de récursive "mantra":
Pour aucun des arguments:
Pour une fonction qui a des arguments:
QU'est-CE.
Certains argumentation: Généralement c'est mauvais pour la lisibilité du code à mettre des {} après une
if ()
instruction dans une ligne. Mais dans ce cas, il est la routine de tous-les-mêmes "mantra". Il ne rompt pas la lisibilité du code, si cette méthode est conforme sur le projet. Et il permet d'économiser votre code à partir de l'abandon de détritus (une seule ligne de code au lieu de cinq).Comme vous le voyez
if(InvokeRequired) {something long}
vous savez juste "cette fonction est possible d'appeler à partir d'un autre thread".Essayer de mettre à jour l'étiquette à l'aide de cette
Créer une variable de classe:
Défini dans le constructeur qui crée votre INTERFACE utilisateur:
Lorsque vous souhaitez mettre à jour l'étiquette:
Vous devez utiliser invoquer et délégué
La plupart des autres réponses sont un peu complexes pour moi sur cette question (je suis novice en C#), donc je suis en train d'écrire la mienne:
J'ai un WPF application et ont défini un travailleur en tant que ci-dessous:
Question:
Solution:
Je suis encore à savoir ce que la ligne ci-dessus, mais il fonctionne.
Pour WinForms:
Solution:
Par exemple, accéder à un contrôle autre que dans le thread actuel:
Il le
lblThreshold
est un Label etSpeed_Threshold
est une variable globale.Lorsque vous êtes dans le thread de l'INTERFACE utilisateur que vous pourriez demander pour son contexte de synchronisation du planificateur de tâches. Il serait de vous donner un TaskScheduler que les horaires de tout sur le thread d'INTERFACE utilisateur.
Alors vous pouvez lier vos tâches de sorte que lorsque le résultat est prêt, puis une autre tâche (ce qui est prévu sur le thread de l'INTERFACE utilisateur) le récupère et lui attribue un label.
Cela fonctionne pour des tâches (pas de fils), qui sont les le moyen privilégié de l'écriture de code concurrente maintenant.
Task.Start
est généralement pas une bonne pratique blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxJe viens de lire les réponses et que cela semble être un sujet très chaud. Je suis actuellement à l'aide .NET 3.5 SP1 et Windows Forms.
La formule bien connue grandement décrites dans les réponses précédentes qui rend l'utilisation de la InvokeRequired propriété couvre la plupart des cas, mais pas dans l'ensemble du bassin.
Que si le Poignée n'a pas encore été créé?
La InvokeRequired de propriété, tel que décrit ici (le Contrôle.InvokeRequired référence du Bien à MSDN) retourne true si l'appel a été fait à partir d'un thread qui n'est pas le thread GUI, false si l'appel a été fait à partir du thread GUI, ou si le Poignée n'était pas encore créé.
Vous pouvez venir à travers une exception si vous voulez avoir un formulaire modal montré et mis à jour par un autre thread. Parce que vous souhaitez que le formulaire ci-modal, vous pouvez effectuer les opérations suivantes:
Et le délégué peut mettre à jour une Étiquette sur l'interface graphique:
Cela peut provoquer une InvalidOperationException si les opérations avant que le label de la mise à jour de "prendre moins de temps" (le lire et de l'interpréter à la simplification de la procédure) que le temps qu'il faut pour que le thread GUI pour créer le Forme's Poignée. Ce qui se passe à l'intérieur de la ShowDialog() méthode.
Vous devriez également vérifier pour la Poignée comme ceci:
Vous pouvez gérer l'opération à effectuer si le Poignée n'a pas encore été créé: Vous pouvez tout simplement ignorer l'interface graphique de mise à jour (comme indiqué dans le code ci-dessus) ou vous pouvez attendre (plus risqué).
Cela devrait répondre à la question.
Facultatif trucs:
Personnellement, je suis venu de codage suivantes:
- Je nourrir mes formulaires mis à jour par un autre thread avec une instance de cette ThreadSafeGuiCommand, et je définir des méthodes de mise à jour de l'interface graphique (dans mon Formulaire) comme ceci:
De cette façon, je suis sûr que je vais avoir mon GUI mis à jour tout ce thread va faire l'appel, éventuellement en attente pour une quantité définie de temps (timeout).
La façon la plus simple je pense:
La façon la plus simple dans les applications WPF est:
Je voulais ajouter un avertissement parce que j'ai remarqué que certaines des solutions les plus simples omettre le
InvokeRequired
vérifier.J'ai remarqué que si votre code s'exécute avant de la poignée de la fenêtre de la commande a été créé (par exemple, avant que le formulaire est affiché),
Invoke
déclenche une exception. Je recommande donc de toujours vérifier surInvokeRequired
avant d'appelerInvoke
ouBeginInvoke
.Même si l'opération est coûteuse en temps (thread.dormir dans mon exemple) - Ce code ne sera PAS verrouiller votre INTERFACE utilisateur:
Je ne pouvais pas obtenir de Microsoft logique derrière ce vilain mise en œuvre, mais vous devez avoir deux fonctions:
Ces fragments de travail pour moi, afin que je puisse faire quelque chose sur un autre thread, puis-je mettre à jour l'interface graphique:
Encore plus simple:
Peut-être un peu l'overdose, mais c'est le genre de façon-je résoudre ce normalement:
Invoque ne sont pas nécessaires ici en raison de la synchronisation.
Le BasicClassThreadExample est juste une sorte de mise en page pour moi, afin de modifier pour l'adapter à vos besoins réels.
Il est simple parce que vous n'avez pas besoin de manipuler la substance dans le thread de l'INTERFACE utilisateur!
Voici un regard nouveau sur un vieux problème dans un style fonctionnel. Si vous gardez le TaskXM classe dans tous vos projets, vous n'avez qu'une ligne de code pour ne jamais vous soucier de la croix-fil des mises à jour à nouveau.
Task.Run
comme diffèrent juste d'appelerawait Task.Run(() => {...});
? Quel est l'avantage de l'indirection ici?Un autre exemple sur le sujet: j'ai fait une classe abstraite, UiSynchronizeModel, qui contient une méthode commune de mise en œuvre:
Votre modèle ou de contrôleur de classe devrait être dérivé de cette classe abstraite. Vous pouvez utiliser n'importe quel motif (tâches ou gérés manuellement threads d'arrière-plan) et l'utilisation de ces méthodes comme ceci:
Tâches exemple:
Fondamentalement la façon de résoudre ce problème quelle que soit la version de framework ou de l'interface graphique sous-jacent de la bibliothèque de type est de sauver le contrôle de la création du thread contexte de Synchronisation pour le thread de travail qui mobilisera le contrôle est lié à l'interaction de la thread de l'interface graphique du fil file d'attente de messages.
Exemple:
Je préfère celui-ci:
De la première à obtenir l'instance de votre formulaire (dans ce cas mainForm), puis il suffit d'utiliser ce code dans un autre thread.
Mettre en commun certaines variables dans une classe séparée pour conserver la valeur.
Exemple:
Et encore un autre générique Contrôle extension de l'approche..
D'abord ajouter une méthode d'extension pour les objets de type Contrôle
et de les appeler comme cela, à partir d'un autre thread d'accéder à un Contrôle nommé objet1 dans l'INTERFACE utilisateur-thread:
..ou comme ce
Dans mon cas (WPF) la solution est simple: les
suffit d'utiliser la synchronisation contexte de l'interface utilisateur,
Approche générale est comme:
Explication:
La réponse est assez comme cette une. Mais utilise plus propre (comme pour moi) et plus récents de la syntaxe. Le point est
InvokeRequired
propriété decontrol
. Il obtient une valeur indiquant si l'appelant doit appeler une méthode invoke lors de la prise des appels de méthode pour le contrôle parce que l'appelant est sur un autre thread que le contrôle a été créé. Donc, si nous appelonscontrol.SetText("some text")
sur le même threadcontrol
a été créé, c'est OK pourText
que cecontrol.Text = text
. Mais sur un autre thread, il provoqueSystem.InvalidOperationException
si on doit l'appeler une méthode viacontrol.Invoke(...)
pour définirText
sur le filcontrol
a été créé.Façon la plus simple est en invoquant comme suit:
Pour atteindre cet objectif dans WPF, je le fais de la façon suivante.