Swing threading et le thread de distribution d'événements

Précédent 1 2 3 4 5 Page 5 Page 5 sur 5

Garder le fil Swing en toute sécurité

La dernière étape de la création d'une interface graphique Swing est de la démarrer. La manière correcte de démarrer une interface graphique Swing diffère aujourd'hui de l'approche initialement prescrite par Sun. Voici à nouveau la citation de la documentation Sun:

Une fois qu'un composant Swing a été réalisé, tout le code qui pourrait affecter ou dépendre de l'état de ce composant doit être exécuté dans le thread de distribution d'événements.

Jetez maintenant ces instructions par la fenêtre, car à la sortie de JSE 1.5, tous les exemples sur le site de Sun ont changé. Depuis ce temps, il est un fait peu connu que vous êtes censé toujours accéder aux composants Swing sur le thread de distribution d'événements pour assurer leur sécurité de thread / accès à un seul thread. La raison de ce changement est simple: bien que votre programme puisse accéder à un composant Swing hors du thread de distribution d'événements avant que le composant ne soit réalisé, l'initialisation de l'interface utilisateur Swing pourrait déclencher quelque chose à exécuter sur le thread de distribution d'événements par la suite, car le component / UI s'attend à tout exécuter sur le thread de distribution d'événements. Le fait que les composants de l'interface graphique s'exécutent sur différents threads rompt le modèle de programmation monothread de Swing.

Le programme du Listing 5 n'est pas tout à fait réaliste, mais il sert à faire valoir mon point.

Listing 5. Accès à l'état du composant Swing à partir de plusieurs threads

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BadSwingButton { public static void main(String args[]) { JFrame frame = new JFrame("Title"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Press Here"); ContainerListener container = new ContainerAdapter() { public void componentAdded(final ContainerEvent e) { SwingWorker worker = new SwingWorker() { protected String doInBackground() throws InterruptedException { Thread.sleep(250); return null; } protected void done() { System.out.println("On the event thread? : " + EventQueue.isDispatchThread()); JButton button = (JButton)e.getChild(); String label = button.getText(); button.setText(label + "0"); } }; worker.execute(); } }; frame.getContentPane().addContainerListener(container); frame.add(button, BorderLayout.CENTER); frame.setSize(200, 200); try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("I'm about to be realized: " + EventQueue.isDispatchThread()); frame.setVisible(true); } }

Notez que la sortie montre du code en cours d'exécution sur le thread principal avant la réalisation de l'interface utilisateur. Cela signifie que le code d'initialisation s'exécute sur un thread tandis qu'un autre code d'interface utilisateur s'exécute sur le thread de distribution d'événements, ce qui rompt le modèle d'accès monothread de Swing:

> java BadSwingButton On the event thread? : true I'm about to be realized: false

Le programme de la liste 5 mettra à jour l'étiquette du bouton à partir de l'écouteur du conteneur lorsque le bouton est ajouté au conteneur. Pour rendre le scénario plus réaliste, imaginez une interface utilisateur qui «compte» les étiquettes et utilise le nombre comme texte dans le titre de la bordure. Naturellement, il faudrait mettre à jour le texte du titre de la bordure dans le fil de distribution d'événements. Pour garder les choses simples, le programme met simplement à jour l'étiquette d'un bouton. Bien qu'il ne soit pas réaliste en fonction, ce programme montre le problème avec chaque programme Swing qui a été écrit depuis le début de Swing. (Ou du moins tous ceux qui suivaient le modèle de threading recommandé trouvé dans les javadocs et les didacticiels en ligne de Sun Microsystems, et même dans mes propres premières éditions de livres de programmation Swing.)

Swing threading fait bien

La façon d'obtenir le bon filetage Swing est d'oublier le dicton original de Sun. Ne vous inquiétez pas de savoir si un composant est réalisé ou non. N'essayez pas de déterminer s'il est sûr d'accéder à quelque chose en dehors du thread de répartition des événements. Cela ne l'est jamais. Au lieu de cela, créez toute l'interface utilisateur sur le thread de répartition des événements. Si vous placez l'appel de création d'interface utilisateur dans son EventQueue.invokeLater()intégralité, tous les accès lors de l'initialisation sont garantis dans le thread de répartition des événements. C'est aussi simple que cela.

Listing 6. Tout à sa place

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class GoodSwingButton { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Title"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Press Here"); ContainerListener container = new ContainerAdapter() { public void componentAdded(final ContainerEvent e) { SwingWorker worker = new SwingWorker() { protected String doInBackground() throws InterruptedException { return null; } protected void done() { System.out.println("On the event thread? : " + EventQueue.isDispatchThread()); JButton button = (JButton)e.getChild(); String label = button.getText(); button.setText(label + "0"); } }; worker.execute(); } }; frame.getContentPane().addContainerListener(container); frame.add(button, BorderLayout.CENTER); frame.setSize(200, 200); System.out.println("I'm about to be realized: " + EventQueue.isDispatchThread()); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }

Exécutez-le maintenant et le programme ci-dessus montrera que l'initialisation et le code du conteneur sont en cours d'exécution sur le thread de distribution d'événements:

> java GoodSwingButton I'm about to be realized: true On the event thread? : true

En conclusion

Le travail supplémentaire pour créer votre interface utilisateur dans le thread de répartition des événements peut sembler inutile au début. Tout le monde fait l'inverse depuis la nuit des temps, après tout. Pourquoi se donner la peine de changer maintenant? Le truc, c'est que nous avons toujours mal agi. Pour vous assurer que vos composants Swing sont correctement accessibles, vous devez toujours créer toute l'interface utilisateur dans le thread de distribution d'événements, comme illustré ici:

Runnable runner = new Runnable() { public void run() { // ...create UI here... } } EventQueue.invokeLater(runner);

Déplacer votre code d'initialisation vers le thread de répartition des événements est le seul moyen de garantir que vos interfaces graphiques Swing sont thread-safe. Oui, cela vous semblera gênant au début, mais le progrès le fait généralement.

John Zukowski joue avec Java depuis plus de 12 ans maintenant, ayant abandonné son état d'esprit C et X-Windows depuis longtemps. Avec 10 livres sur des sujets allant de Swing aux collections en passant par Java SE 6, John fait maintenant du conseil en technologie stratégique via son entreprise, JZ Ventures, Inc.

En savoir plus sur ce sujet

  • Apprenez-en davantage sur la programmation Swing et le fil de distribution d'événements de l'un des maîtres du développement de bureau Java: Chet Haase on maximizing Swing and Java 2D (JavaWorld Java Technology Insider podcast, août 2007).
  • «Personnaliser SwingWorker pour améliorer les interfaces graphiques Swing» (Yexin Chen, JavaWorld, juin 2003) approfondit certains des défis de filetage Swing abordés dans cet article et explique comment une personnalisation SwingWorkerpeut fournir le muscle nécessaire pour les contourner.
  • "Java et gestion des événements" (Todd Sundsted, JavaWorld, août 1996) est une introduction à la gestion des événements vers AWT.
  • "Speed ​​up listener notification" (Robert Hastings, JavaWorld, février 2000) introduit la spécification JavaBeans 1.0 pour l'enregistrement et la notification d'événements.
  • «Obtenez de solides performances avec les threads, partie 1» (Jeff Friesen, JavaWorld, mai 2002) présente les threads Java. Voir la partie 2 pour une réponse à la question: Pourquoi avons-nous besoin d'une synchronisation?
  • «Executing tasks in threads» est un extrait JavaWorld de Java Concurrency in Practice (Brian Goetz, et al., Addison Wesley Professional, mai 2006) qui encourage la programmation de threads basée sur les tâches et introduit un cadre d'exécution pour la gestion des tâches.
  • "Threads and Swing" (Hans Muller et Kathy Walrath, avril 1998) est l'une des premières références officielles pour le filetage Swing. Elle inclut la désormais célèbre (et erronée) "règle de thread unique".
  • La création d'une interface graphique avec JFC / Swing est la page complète du tutoriel Java pour la programmation de l'interface graphique Swing.
  • "Concurrency in Swing" est un tutoriel sur le sentier Swing qui comprend une introduction à la SwingWorkerclasse.
  • JSR 296: Swing Application Framework est actuellement une spécification en cours. Consultez également «Utilisation du cadre d'application Swing» (John O'Conner, Sun Developer Network, juillet 2007) pour en savoir plus sur cette prochaine étape de l'évolution de la programmation Swing GUI.
  • L'ensemble de la référence Java AWT (John Zukowski, O'Reilly, mars 1997) est disponible gratuitement sur le catalogue en ligne O'Reilly.
  • John's Definitive Guide to Java Swing, Third Edition (Apress, juin 2005) est entièrement mis à jour pour Java Standard Edition version 5.0. Lisez un chapitre de prévisualisation du livre ici même sur JavaWorld !
  • Visitez le centre de recherche JavaWorld Swing / GUI pour plus d'articles sur la programmation Swing et le développement de bureau Java.
  • Consultez également les forums des développeurs JavaWorld pour des discussions et des questions / réponses liées à la programmation de bureau Swing et Java.

Cette histoire, "Swing threading and the event-dispatch thread" a été publiée à l'origine par JavaWorld.