Timer & Thread

Thread & Swing

Evènement synchrone et asynchrone

Traitement en tâche de fond

Evènement d'affichage

Timer Swing

Application graphique multi-thread

En règle général, les toolkits graphique ne permettent pas d'avoir de multiples threads qui modifient concuramment la hiérarchie des composants.
Une interface graphique est mono-threadée.

De plus, en AWT/Swing, on ne choisi pas la thread qui fait les changements.

La seul Thread (EventDispatchThread) qui à le droit d'effectuer des changements sur les composants est celle qui s'occupe de traiter les évènements.

Celle-ci est créée automatiquement.

Application graphique multi-thread

Le code de modification graphique ne peut être exécuté que par l'EventDispatchThread.

Problème : Comment faire si l'application possède une ou plusieurs tâches à exécuter en tâche de fond et qui modifie l'interface graphique.

Solution : Demander à l'EventDispatchThread d'exécuter du code.

Poster des évènements

Envoyer du code a exécuter par l'EventDispatchThread dans un évènement qui sera posté dans la queue d'évènement.

Plateforme

Mouse

Keyboard

GetNextEvent()

PostEvent()

Queue
d'évènement

EventQueue

La classe EventQueue possède 3 méthodes statiques :

Savoir si la thread courante est l'EventDispatchThread :
EventQueue.isDispatchThread()

Demande à l'EventDispatchThread d'exécuter
un code (Runnable) :

Qui rend à la main immédiatement :
EventQueue.invokeLater(Runnable code)

Qui rend la main une fois le code exécuter :
EventQueue.invokeAndWait(Runnable code)

isDispatchThread

Exemple de test pour savoir quelle thread exécute quoi.

static void printIsDispatchThread() {

System.out.println(Thread.currentThread()+" "+

EventQueue.isDispatchThread());

}

public static void main(String[] args) {

JFrame frame=new JFrame("DispatchThreadExample");

JButton button=new JButton("Test");

button.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

printIsDispatchThread();

}

});

frame.setContentPane(button);

frame.pack();

frame.setVisible(true);

printIsDispatchThread();

}

Traitement en tâche de fond

Exemple de traitement en tâche de fond.

final JProgressBar progressBar=new JProgressBar(0,100);

final JButton button=new JButton("start");

button.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

button.setEnabled(false);

new Thread() {

public void run() {

for(int i=1;i<=100;i++) {

// truc long

final int percent=i;

EventQueue.invokeLater(new Runnable() {

public void run() {

progressBar.setValue(percent);

}

});

}

try {

EventQueue.invokeAndWait(new Runnable() {

public void run() {

button.setEnabled(true);

}

});

} catch (Exception e) { //MAL }

}

}.start(); } });

Evénement sychrone/asynchrone

Poster un code dans la queue d'évènement :

Appel synchrone	(invokeAndWait())

Appel asynchrone	(invokeLater())

Quand choisir l'un ou l'autre :

Controle d'accès (setEnable()) à un composant
=> évènement synchrone

Modification d'affichage pour l'utilisateur
=> évènement asynchrone

Changement sur l'interface permet à l'utilisateur d'effectuer (ou de ne pas effectuer) une action
=> évènement synchrone

Trop d'appels asynchrones

Problème avec appel asynchrone => flood de la queue d'évènements.

Si trop d'appels asynchrones sont générés, comme ceux-ci sont postés dans la queue d'évènement, ils empèchent le traitement des évènements normaux.

Il faut donc créer des appels asynchrones avec parcimonie.

Conclusion : S'il y a de nombreux traitements consécutifs, utiliser les appels synchrones.

Ajout à un modèle en tâche de fond

Comment ajouter tous les répertoires du disque dans une liste

class LazyListModel extends AbstractListModel {

public int getSize() {

return list.size();

}

public File getElementAt(int index) {

return list.get(index);

}

public void safeAdd(final File file) { // à écrire

}

public void traverse(File file) {

for (File file:file.listFiles(filter)) {

safeAdd(file);

traverse(file);

}

}

final ArrayList<File> list=new ArrayList<File>();

private static final FileFilter filter=new FileFilter() {

public boolean accept(File file) {

return file.isDirectory();

}

});

}

Ajout de façon asynchrone

safeAdd() avec un appel asynchrone

Génére autant d'évènement asynchrone qu'il y a de repertoire sur le disque.

Conclusion : Pas très efficace.

public void safeAdd(final File file) {

EventQueue.invokeLater(new Runnable() {

public void run() {

int index= list.size();

list.add(file);

fireIntervalAdded(LazyListModel.this, index, index);

}

});

}

Ajout de façon synchrone

safeAdd() avec un appel synchrone

public void safeAdd(final File file) {

try {

EventQueue.invokeAndWait(new Runnable() {

public void run() {

int index= list.size();

list.add(file);

fireIntervalAdded(LazyListModel.this, index, index);

}

});

} catch (InterruptedException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

Throwable cause= e.getCause();

if (cause instanceof RuntimeException)

throw (RuntimeException)cause;

else

if (cause instanceof Error)

throw (Error)cause;

else

throw new Error(cause);

}

}

Ajout de façon synchrone par lots

safeAdd() avec un tableau de fichier,
génère moins d'évènements.

public void safeAdd(final File[] files) {

try {

EventQueue.invokeAndWait(new Runnable() {

public void run() {

int index= list.size();

Collections.addAll(list,files);

fireIntervalAdded(EffectiveLazyListModel.this,
index, index+files.length-1);

}

});

} catch (InterruptedException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

... //blah blah

} } }

Méthodes AWT/Swing Threadsafe

Il existe deux méthodes sur un composant qui peuvent être appelées par n'importe quelle Thread.

En effet, ces méthodes postent un évènement dans la queue d'évènement

Demande de raffraichissement :
Component.repaint()

Prendre en compte modification de la hiérarchie :
JComponent.revalidate()

Demande d'affichage

Les demandes de rafraîchissement d'un composant sont traitées de deux façons différentes :

Traitement direct,
si l'on est dans l'EventDispatchThread :
Component.paintImmediately()

Traitement en postant un évènement PaintEvent,
si l'on est dans une autre Thread :
Component.repaint()

Les évènements PaintEvent sont coalescents,
plusieurs évènements deviennent un seul.

Le Timer Swing

Swing, possède un mécanisme de Timer qui poste des évènements TimerEvent.

Le temps (delay) entre deux déclenchements d'évènement est paramètrable.

Constructeur

Utilise un mécanisme de listener :
add/removeActionListener()

Démarre et arrête un timer dans n'importe
quelle Thread :
start()/stop()

Timer(int delay, ActionListener listener);

Le Timer Swing - 2

Indique si le timer tourne
isRunning()

Fixe le délai avant un premier appel aux listeners
get/setInitialDelay(int initialDelay)

Fixe le délai entre deux appels aux listeners
get/setDelay(int delay)

Indique si le timer répète les appels aux listeners
is/setRepeat(boolean repeat)

Regroupe les délais dépassés
is/setCoalesce(boolean coalesce)

Tous les délais sont fixés en milisecondes
de temps "réel".

Exemple de Timer

Exemple de timer que l'on paut démarrer et arrêter.

JFrame frame=new JFrame("TimerExample");

final JPanel panel=new JPanel();

final Timer timer=new Timer(300,new ActionListener() {

public void actionPerformed(ActionEvent e) {

for(int i=0;i<panel.getComponentCount();i++)

panel.getComponent(i).setBackground(getRandomColor());

}

});

final JButton timerButton=new JButton("start");

timerButton.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

if (timer.isRunning())

timer.stop();

else

timer.start();

timerButton.setText((timer.isRunning())?"stop":"start");

}

});

panel.add(timerButton);

for(int i=0;i<5;i++)

panel.add(new JButton("button "+i));

Berstel Jean

2001-03-14T09:29:37