Quelle est la façon la plus simple de mettre à jour un à Label
partir d'un autre Thread
?
Je suis en
Form
cours d'exécutionthread1
, et à partir de là, je commence un autre thread (thread2
).Tout en
thread2
est le traitement de certains fichiers , je voudrais mettre à jour unLabel
sur laForm
avec l'état actuel desthread2
travaux de ».
Comment pourrais-je faire ça?
c#
.net
multithreading
winforms
user-interface
CruelIO
la source
la source
Réponses:
Pour .NET 2.0, voici un joli morceau de code que j'ai écrit qui fait exactement ce que vous voulez, et fonctionne pour n'importe quelle propriété sur un
Control
:Appelez ça comme ceci:
Si vous utilisez .NET 3.0 ou supérieur, vous pouvez réécrire la méthode ci-dessus en tant que méthode d'extension de la
Control
classe, ce qui simplifierait alors l'appel à:MISE À JOUR 05/10/2010:
Pour .NET 3.0, vous devez utiliser ce code:
qui utilise les expressions LINQ et lambda pour permettre une syntaxe beaucoup plus propre, plus simple et plus sûre:
Non seulement le nom de la propriété est maintenant vérifié au moment de la compilation, mais le type de la propriété l'est également, il est donc impossible (par exemple) d'attribuer une valeur de chaîne à une propriété booléenne, et donc de provoquer une exception d'exécution.
Malheureusement, cela n'empêche personne de faire des choses stupides telles que transmettre
Control
la propriété et la valeur d'un autre, donc les éléments suivants seront joyeusement compilés:Par conséquent, j'ai ajouté les vérifications d'exécution pour garantir que la propriété transmise appartient réellement à la propriété
Control
laquelle la méthode est appelée. Pas parfait, mais toujours beaucoup mieux que la version .NET 2.0.Si quelqu'un a d'autres suggestions sur la façon d'améliorer ce code pour la sécurité à la compilation, veuillez commenter!
la source
SetControlPropertyThreadSafe(myLabel, "Text", status)
être appelé depuis un autre module ou classe ou formulaireLa manière la plus simple est une méthode anonyme passée dans
Label.Invoke
:Notez que
Invoke
bloque l'exécution jusqu'à ce qu'elle se termine - c'est du code synchrone. La question ne concerne pas le code asynchrone, mais il y a beaucoup de contenu sur Stack Overflow sur l'écriture de code asynchrone lorsque vous voulez en savoir plus.la source
Gérer un long travail
Depuis .NET 4.5 et C # 5.0, vous devez utiliser le modèle asynchrone basé sur les tâches (TAP) avec async - attendez les mots clés dans tous les domaines (y compris l'interface graphique):
au lieu du modèle de programmation asynchrone (APM) et du modèle asynchrone basé sur les événements (EAP) (ce dernier inclut la classe BackgroundWorker ).
Ensuite, la solution recommandée pour un nouveau développement est:
Implémentation asynchrone d'un gestionnaire d'événements (Oui, c'est tout):
Implémentation du deuxième thread qui notifie le thread d'interface utilisateur:
Remarquez ce qui suit:
Pour des exemples plus verbeux, voir: L'avenir de C #: de bonnes choses viennent à ceux qui «attendent» par Joseph Albahari .
Voir aussi à propos du modèle de thread d'interface utilisateur concept de .
Gestion des exceptions
L'extrait ci-dessous est un exemple de la façon de gérer les exceptions et de basculer la
Enabled
propriété du bouton pour empêcher plusieurs clics lors de l'exécution en arrière-plan.la source
SecondThreadConcern.LongWork()
lève une exception, peut-elle être interceptée par le thread d'interface utilisateur? C'est un excellent article, au fait.Task.Delay(500).Wait()
? Quel est l'intérêt de créer une tâche pour simplement bloquer le thread actuel? Vous ne devez jamais bloquer un thread de pool de threads!Variation de la solution la plus simple de Marc Gravell pour .NET 4:
Ou utilisez plutôt délégué d'action:
Voir ici pour une comparaison des deux: MethodInvoker vs Action for Control.
la source
this.refresh()
forcer l'invalidation et repeindre l'interface graphique .. si cela est utile ..Déclenchez et oubliez la méthode d'extension pour .NET 3.5+
Cela peut être appelé à l'aide de la ligne de code suivante:
la source
@this
est simplement le nom de la variable, dans ce cas la référence au contrôle actuel appelant l'extension. Vous pouvez le renommer en source, ou tout ce qui fait flotter votre bateau. J'utilise@this
, car il fait référence à «ce contrôle» qui appelle l'extension et est cohérent (dans ma tête, au moins) avec l'utilisation du mot-clé «this» dans le code normal (sans extension).OnUIThread
plutôt queUIThread
.RunOnUiThread
. Mais c'est juste un goût personnel.Voici la manière classique de procéder:
Votre thread de travail a un événement. Votre thread d'interface utilisateur démarre un autre thread pour effectuer le travail et connecte cet événement de travail afin que vous puissiez afficher l'état du thread de travail.
Ensuite, dans l'interface utilisateur, vous devez croiser les fils pour changer le contrôle réel ... comme une étiquette ou une barre de progression.
la source
La solution simple est d'utiliser
Control.Invoke
.la source
Le code de threading est souvent bogué et toujours difficile à tester. Vous n'avez pas besoin d'écrire du code de thread pour mettre à jour l'interface utilisateur à partir d'une tâche en arrière-plan. Utilisez simplement la classe BackgroundWorker pour exécuter la tâche et sa méthode ReportProgress pour mettre à jour l'interface utilisateur. Habituellement, vous signalez simplement un pourcentage achevé, mais il y a une autre surcharge qui inclut un objet d'état. Voici un exemple qui rapporte simplement un objet chaîne:
C'est bien si vous voulez toujours mettre à jour le même champ. Si vous avez des mises à jour plus compliquées à effectuer, vous pouvez définir une classe pour représenter l'état de l'interface utilisateur et la transmettre à la méthode ReportProgress.
Une dernière chose, assurez-vous de définir l'
WorkerReportsProgress
indicateur, sinon laReportProgress
méthode sera complètement ignorée.la source
backgroundWorker1_RunWorkerCompleted
.La grande majorité des réponses utilisent
Control.Invoke
ce qui est une condition de concurrence qui attend de se produire . Par exemple, considérez la réponse acceptée:Si l'utilisateur ferme le formulaire juste avant
this.Invoke
son appel (rappelez-vous,this
est l'Form
objet), unObjectDisposedException
sera probablement renvoyé.La solution est d'utiliser
SynchronizationContext
, en particulierSynchronizationContext.Current
comme le suggère hamilton.danielb (les autres réponses reposent sur desSynchronizationContext
implémentations spécifiques, ce qui est complètement inutile). Je modifierais légèrement son code pour l'utiliserSynchronizationContext.Post
plutôt que de le faireSynchronizationContext.Send
(car il n'est généralement pas nécessaire d'attendre le thread de travail):Notez que sur .NET 4.0 et supérieur, vous devez vraiment utiliser des tâches pour les opérations asynchrones. Voir la réponse de n-san pour l'approche équivalente basée sur les tâches (en utilisant
TaskScheduler.FromCurrentSynchronizationContext
).Enfin, sur .NET 4.5 et plus, vous pouvez également utiliser
Progress<T>
(qui capture essentiellementSynchronizationContext.Current
lors de sa création) comme démontré par Ryszard Dżegan pour les cas où l'opération de longue durée doit exécuter du code d'interface utilisateur tout en continuant à fonctionner.la source
Vous devrez vous assurer que la mise à jour a lieu sur le bon thread; le thread d'interface utilisateur.
Pour ce faire, vous devrez appeler le gestionnaire d'événements au lieu de l'appeler directement.
Vous pouvez le faire en organisant votre événement comme ceci:
(Le code est tapé ici de ma tête, donc je n'ai pas vérifié la syntaxe correcte, etc., mais cela devrait vous aider.)
Notez que le code ci-dessus ne fonctionnera pas sur les projets WPF, car les contrôles WPF n'implémentent pas l'
ISynchronizeInvoke
interface.Afin de vous assurer que le code ci - dessus fonctionne avec Windows Forms et WPF, et toutes les autres plates - formes, vous pouvez consulter les
AsyncOperation
,AsyncOperationManager
et lesSynchronizationContext
classes.Afin de déclencher facilement des événements de cette façon, j'ai créé une méthode d'extension, qui me permet de simplifier le déclenchement d'un événement en appelant simplement:
Bien sûr, vous pouvez également utiliser la classe BackGroundWorker, qui résumera cette question pour vous.
la source
Vous devrez appeler la méthode sur le thread GUI. Vous pouvez le faire en appelant Control.Invoke.
Par exemple:
la source
En raison de la banalité du scénario, j'aurais en fait le sondage du thread d'interface utilisateur pour le statut. Je pense que vous constaterez qu'il peut être assez élégant.
L'approche évite l'opération de marshaling requise lors de l'utilisation des méthodes
ISynchronizeInvoke.Invoke
etISynchronizeInvoke.BeginInvoke
. Il n'y a rien de mal à utiliser la technique de marshaling, mais il y a quelques mises en garde dont vous devez être conscient.BeginInvoke
trop fréquemment, car cela pourrait dépasser la pompe de messages.Invoke
sur le thread de travail est un appel bloquant. Cela interrompra temporairement le travail en cours dans ce thread.La stratégie que je propose dans cette réponse inverse les rôles de communication des threads. Au lieu du thread de travail poussant les données, le thread d'interface utilisateur l'interroge. C'est un modèle commun utilisé dans de nombreux scénarios. Puisque tout ce que vous voulez faire est d'afficher les informations de progression du thread de travail, je pense que vous constaterez que cette solution est une excellente alternative à la solution de marshaling. Il présente les avantages suivants.
Control.Invoke
ouControl.BeginInvoke
approche qui les couple étroitement.la source
Elapsed
événements, vous utilisez une méthode membre afin de pouvoir supprimer le minuteur lorsque le formulaire est supprimé ...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
et l'assigner viam_Timer.Elapsed += handler;
, plus tard dans le contexte de disposition, faism_Timer.Elapsed -= handler;
-je un bon choix? Et pour l'élimination / fermeture suivant les conseils comme discuté ici .Aucune des choses Invoke dans les réponses précédentes n'est nécessaire.
Vous devez regarder WindowsFormsSynchronizationContext:
la source
Celle-ci est similaire à la solution ci-dessus utilisant .NET Framework 3.0, mais elle a résolu le problème de la prise en charge de la sécurité au moment de la compilation .
Utiliser:
Le compilateur échouera si l'utilisateur transmet le mauvais type de données.
la source
Salvete! Après avoir cherché cette question, j'ai trouvé les réponses de FrankG et Oregon Ghost étaient les plus simples et les plus utiles pour moi. Maintenant, je code en Visual Basic et j'ai exécuté cet extrait de code via un convertisseur; donc je ne sais pas trop comment ça se passe.
J'ai un formulaire de dialogue appelé
form_Diagnostics,
qui a une boîte de texte riche, appeléupdateDiagWindow,
que j'utilise comme une sorte d'affichage de journalisation. J'avais besoin de pouvoir mettre à jour son texte à partir de toutes les discussions. Les lignes supplémentaires permettent à la fenêtre de défiler automatiquement jusqu'aux lignes les plus récentes.Et donc, je peux maintenant mettre à jour l'affichage avec une seule ligne, de n'importe où dans le programme entier de la manière dont vous pensez que cela fonctionnerait sans filetage:
Code principal (insérez-le dans le code de classe de votre formulaire):
la source
À de nombreuses fins, c'est aussi simple que cela:
"serviceGUI ()" est une méthode de niveau GUI dans le formulaire (this) qui peut changer autant de contrôles que vous le souhaitez. Appelez "updateGUI ()" à partir de l'autre thread. Des paramètres peuvent être ajoutés pour transmettre des valeurs, ou (probablement plus rapidement) utiliser des variables de portée de classe avec des verrous sur eux s'il y a une possibilité de conflit entre les threads y accédant qui pourrait provoquer une instabilité. Utilisez BeginInvoke au lieu d'Invoke si le thread non GUI est critique en temps (en gardant à l'esprit l'avertissement de Brian Gideon).
la source
Ceci dans ma variation C # 3.0 de la solution d'Ian Kemp:
Vous l'appelez comme ceci:
Sinon, l'original est une très bonne solution.
la source
Notez que
BeginInvoke()
c'est préférable àInvoke()
car il est moins susceptible de provoquer des blocages (cependant, ce n'est pas un problème ici lorsque vous attribuez simplement du texte à une étiquette):Lors de l'utilisation
Invoke()
vous vous attendez le retour de la méthode. Maintenant, il se peut que vous fassiez quelque chose dans le code invoqué qui devra attendre le thread, ce qui peut ne pas être immédiatement évident s'il est enterré dans certaines fonctions que vous appelez, ce qui peut se produire indirectement via des gestionnaires d'événements. Vous attendriez donc le fil, le fil vous attendrait et vous êtes dans une impasse.Cela a en fait provoqué le blocage de certains de nos logiciels publiés. Il était assez facile à corriger en remplaçant
Invoke()
parBeginInvoke()
. Sauf si vous avez besoin d'un fonctionnement synchrone, ce qui peut être le cas si vous avez besoin d'une valeur de retour, utilisezBeginInvoke()
.la source
Lorsque 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, cela m'a davantage troublé en donnant des exemples de
MethodInvoker
et bla bla bla. J'ai donc décidé de le résoudre par moi-même. Voici ma solution:Faites un délégué comme ceci:
Vous pouvez appeler cette fonction dans un nouveau fil comme celui-ci
Ne soyez pas confondu avec
Thread(() => .....)
. J'utilise une fonction anonyme ou une expression lambda lorsque je travaille sur un thread. Pour réduire les lignes de code, vous pouvez également utiliser laThreadStart(..)
méthode que je ne suis pas censé expliquer ici.la source
Utilisez simplement quelque chose comme ceci:
la source
e.ProgressPercentage
, n'êtes-vous pas déjà dans le thread d'interface utilisateur de la méthode que vous appelez cela?Vous pouvez utiliser le délégué déjà existant
Action
:la source
Ma version consiste à insérer une ligne de "mantra" récursif:
Pour aucun argument:
Pour une fonction qui a des arguments:
C'est ça .
Quelques arguments : Habituellement, il est mauvais pour la lisibilité du code de mettre {} après une
if ()
instruction sur une seule ligne. Mais dans ce cas, c'est tout de même un "mantra" de routine. Il ne rompt pas la lisibilité du code si cette méthode est cohérente sur le projet. Et il sauve votre code des ordures (une ligne de code au lieu de cinq).Comme vous le voyez,
if(InvokeRequired) {something long}
vous savez simplement que "cette fonction est sûre à appeler depuis un autre thread".la source
Essayez de rafraîchir l'étiquette en utilisant ceci
la source
Créez une variable de classe:
Définissez-le dans le constructeur qui crée votre interface utilisateur:
Lorsque vous souhaitez mettre à jour l'étiquette:
la source
Vous devez utiliser invoke et delegate
la source
La plupart des autres réponses sont un peu complexes pour moi sur cette question (je suis nouveau en C #), donc j'écris la mienne:
J'ai une application WPF et j'ai défini un travailleur comme ci-dessous:
Problème:
Solution:
Je ne sais pas encore ce que signifie la ligne ci-dessus, mais cela fonctionne.
Pour WinForms :
Solution:
la source
La façon la plus simple, je pense:
la source
Par exemple, accédez à un contrôle autre que dans le thread actuel:
Il
lblThreshold
y a une étiquette etSpeed_Threshold
une variable globale.la source
Lorsque vous êtes dans le thread d'interface utilisateur, vous pouvez lui demander son planificateur de tâches de contexte de synchronisation. Cela vous donnerait un TaskScheduler qui planifie tout sur le thread d'interface utilisateur.
Ensuite, vous pouvez enchaîner vos tâches de sorte que lorsque le résultat est prêt, une autre tâche (planifiée sur le thread d'interface utilisateur) le sélectionne et l'affecte à une étiquette.
Cela fonctionne pour les tâches (pas pour les threads) qui sont le moyen préféré d'écrire du code simultané maintenant .
la source
Task.Start
n'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 cela semble être un sujet très brûlant. J'utilise actuellement .NET 3.5 SP1 et Windows Forms.
La formule bien connue largement décrite dans les réponses précédentes qui utilise la propriété InvokeRequired couvre la plupart des cas, mais pas l'ensemble du pool.
Que faire si la poignée n'a pas encore été créée?
La propriété InvokeRequired , comme décrit ici (référence de propriété Control.InvokeRequired à MSDN) renvoie true si l'appel a été effectué à partir d'un thread qui n'est pas le thread GUI, false soit si l'appel a été effectué à partir du thread GUI, ou si le handle a été pas encore créé.
Vous pouvez rencontrer une exception si vous souhaitez qu'un formulaire modal soit affiché et mis à jour par un autre thread. Étant donné que vous souhaitez que ce formulaire s'affiche de manière modale, 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 la mise à jour « prennent moins de temps » de l'étiquette ( le lire et l' interpréter comme une simplification) que le temps qu'il faut pour le thread GUI pour créer le formulaire de poignée . Cela se produit dans la méthode ShowDialog () .
Vous devez également vérifier la poignée comme ceci:
Vous pouvez gérer l'opération à effectuer si le handle n'a pas encore été créé: vous pouvez simplement ignorer la mise à jour de l'interface graphique (comme indiqué dans le code ci-dessus) ou vous pouvez attendre (plus risqué). Cela devrait répondre à la question.
Trucs optionnels: Personnellement, je suis venu coder ce qui suit:
Je nourris mes formulaires qui sont mis à jour par un autre thread avec une instance de ce ThreadSafeGuiCommand , et je définis des méthodes qui mettent à jour l'interface graphique (dans mon formulaire) comme ceci:
De cette façon, je suis sûr que mon interface graphique sera mise à jour quel que soit le thread qui fera l'appel, en attendant éventuellement une durée bien définie (le délai d'expiration).
la source