OVH Cloud OVH Cloud

volatile et template

29 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour,
J'ai une application multi threadée avec le thread principal qui lance un
autre thread en lui donnant en paramètre un smart pointer (c'est pas tout à
fait ça mais presque).
Je voudrais déclarer dans mon thread secondaire le smart pointer comme
volatile, mais ça marche pas (à l'utilisation):

boost::shared_ptr<int> v1( new int );
if ( v1 ) // ok
{
}

volatile boost::shared_ptr<int> v2( new int );
if ( v2 ) // erreur de compilation ici
{
}

comment m'en sortir ?
Merci.

--
Aurélien REGAT-BARREL

10 réponses

1 2 3
Avatar
Aurélien REGAT-BARREL
Dans son cas, la solution serait sans doute celle imposée par
Qt, que je connais pas. Dans les systèmes de fenêtrage que je
connais, toutes les mises à jours des graphiques se font dans un
thread particulier, qui est aussi celui que gère les
évenemments. Pour mettre à jour un barre d'avancement, par
exemple, on envoie un évenement au thread gestionnaire des
événements, qui s'en occupe. L'alternatif, c'est (en supposant
un MVC) que le thread de calcul fasse la mise à jour du modèle,
puis envoie un événemment au thread d'affichage pour l'informer
qu'il y a eu un changement.


C'est exactement cela. Le probleme est dans l'envoie de l'évènement. Pour
cela, je dois spécifier la fenêtre destinatrice. Mon probleme : si par la
suite cette fenêtre est détruite, l'envoie d'évènement fait tout crasher
(pointeur null reference). Qt aurait pu être mieux fouttu sur ce point mais
c'est pas le cas. Alors j'ai pris mes précautions, ça ne se produira plus,
mais bon j'aime pas avoir ce genre de sournoiserie qui traine...
Ce qui m'est imposé par Qt, c'est d'utiliser un QObject* comme destinataire
de l'évènement.
Le probleme c'est que si ce QObject* est détruit par la suite, boum
badaboum.
Solution : j'utilise un QGuardedPtr<QObject>, qui a une particularité : être
automatiquement mis à NULL si l'objet pointé est détruit. Ca marche très
bien.

Ma question : le fait que ce QGuardedPtr soit mis à NULL par un autre
thread, je me demande si une optimisation du compilo ne risque pas de lui
faire louper l'info.
C'est un peu comme si on avait ça:

std::shared_ptr<int> ptr( new int );

thread1:
while ( ptr != 0 ) { /* ... */ }

thread2:
/* blablabla */
ptr.reset();

sauf que apparement shared_ptr est thread safe. Mais vous pigez ma question
: le test:
while ( ptr != 0 )
fait dans thread1 ne risque-t-il pas d'être faussé par la modif de thread2
"en parallèle" ?

Bon je réalise que rien n'empêche ptr d'être invalidé après le test... Bref,
je vais devoir faire de la synchronisation.

Dans le premier cas, il y a deux solutions :

1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.

Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.


Y'a plus simple dans mon cas : un QGuardedPtr est automatiquement mis à NULL
par Qt une fois l'objet pointé déréférencé.

Et ce n'est pas évident pour le
faire marcher correctement -- en gros, je dirais que
l'utilisation du pointeur en tant que pointeur ne doit avoir
lieu que dans le thread d'affichage, qui doit aussi être le
seul thread qui pourrait détruire l'objet d'affichage.

Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui gère la
destruction ; il peuvent donc bien s'arranger pour que la
destruction n'a pas lieu à un moment importun. Ici, c'est Qt
qui le gère, et il faut être certain que Qt ne le fait pas
quand on a déjà extrait le pointeur brut, mais qu'on n'en a
pas encore servi. AMHA, la solution qui s'impose, c'est que
le seul thread qui se sert du pointeur brut, c'est le même
thread que celui qui pourrait éventuellement faire un delete
sur l'objet.


Y'a pas de delete. Dans mon cas ça s'utilise comme ça:

void MainWnd::OnDoSomething()
{
DoSomethingDialog dialog( this );
dialog.exec();
}

et le exec() lance un thread et affiche une barre de progression qui suit
l'évolution du traitement.
Si le programmeur n'a pas géré la fermeture de DoSomethingDialog de manière
à interrompre le thread / ou attendre qu'il se termine, exec() rend la main,
on sort de OnDoSomething() et le dialog est détruit, mais le thread lui
continue d'envoyer un feedback... sur &dialog, et...

2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.


Je suis obligé de passer par un QObject* pour envoyer un évènement, car
c'est un mécanisme interne à Qt. Le QObject indiqué reçoit alors l'évènement
via une fonction virtuelle dédiée.

Dans le deuxième cas, il faut bien que le thread de calcul ait
accès à l'objet d'affichage.


Ca c'est gênant, ça me fait une dépendance croisée FenetreAffichage <->
ThreadCalcul.

Dans ce cas-là, il faut
impérativement 1) qu'il soit observeur, pour que le pointeur
soit mis à null lors d'un delete, et 2) qu'il detient un lock
aussi longtemps qu'il a une copie du pointeur, et que ce même
lock protège la deletion (c-à-d qu'autant qu'il a le lock, on ne
peut pas deleter).


Je ne peux empêcher le delete/destruction, ce serait fâcheux. Dans ce cas ce
n'est plus le thread principal qui controle le thread de calcul, mais le 2°
qui peut bloquer le 1°, et alors la message pump l'est aussi, et donc toute
l'appli aussi, y compris l'envoie d'évènements, plus rien ne fonctionne...
Je préfère l'autre sens : le thread d'affichage tue le process avant de
fermer le dialog, ou plus proprement lui dit de s'arrêter (ce qui est
logique même si je n'avais pas ce probleme).

Pour 2), j'aime plutôt l'idée d'un pointeur
intelligent dont le constructeur aquiert le lock, et le dernier
destructeur le libère. On peut se servir de boost::smart_ptr
ici, à condition d'acquérir le lock avant de créer le pointeur,
et que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère le
lock (et qui ne delete rien). Évidemment, il faut que le code
qui effectue le delete de l'objet prend le même lock. (Je suis
assez scéptique sur la prise d'un lock dans un destructeur. Mais
à la rigueur, comme première action dans le destructeur de la
classe la plus dérivée. Ce qu'il faut tenir en compte, c'est
qu'entre le moment que la décision a été prise de detruire
l'objet et le moment que le thread d'affichage prend le lock, le
thread de calcul peut interrompre, obtenir un pointeur à l'objet
d'affichage, et s'en servir. Or, si on est déjà dans le
destructeur...)


Oui je comprend. Mais je vais faire plus simple. Au lieu de passer un
QObject* simple à mon thread de calcul (pour envoyer le feedback), je vais
lui donner une structure "concurrent access safe" qui permet de savoir si
l'arrêt du traitement a été demandé. Si c'est le cas il n'envoie plus
d'évènements et se termine (mais je garde mon QGuardedPtr pour une double
sécurité, et parce que je suis content de lui avoir touvé une utilité :-).
Merci pour tes idées.

--
Aurélien REGAT-BARREL

Avatar
Aurélien REGAT-BARREL
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.


A quoi sert volatile alors ?

Sinon c'est pas spécifique Windows. Je développe sous Windows certes, mais
Qt est portable (Win/Unix/Mac) et je souhaite conserver cette option, on
sait jamais. Y'a ce qu'il faut pour la synchro classique. Je vais m'en
servir pour demander au thread de s'arrêter.

--
Aurélien REGAT-BARREL

Avatar
Aurélien REGAT-BARREL
fenetre-> MiseAJourAffichage();


Pas possible. L'appel direct comme n'est pas applicable dans mon cas, car
MiseAJour() crée un timer (pour simuler l'avancement) et alors c'est le
thread de calcul qui crée le timer, et comme ce dernier est associé à une
fenêtre créée par un autre thread, ça marche pas, du moins sous Windows
(limitation de SetTimer ou de Qt, je sais pas).

Donc je dois envoyer (en asynchrone) un évènement que la fenêtre cible va
recevoir et c'est elle qui va faire sa popotte.

http://perso.edulang.com/fclc++/critical_section.h.zip


Merci mais je vais utiliser un bon vieux Mutex de chez Qt, car je souhaite
rester le plus portable possible.

--
Aurélien REGAT-BARREL

Avatar
Aurélien REGAT-BARREL
La question qui est derriere cela c'est : le systeme de fenetrage
est-il thread-safe ?


Je doute qu'il y ait un framework dont chaque classe / fonction est thread
safe, surtout en ce qui concerne les classes de fenêtrage. Dans le cas de
Qt, les classes de synchro le sont (Mutex, Semaphore, ...), plus quelques
fonctions de communication par évènements et la classe de QThreadStorage.
Sinon la classe de base QObject est réentrante, mais à part ça pas grand
chose. Ce qui est un argument supplémentaire contre la proposition de Fabien
qui travaille directement la fenêtre de contrôle depuis le thread de calcul.

Dans le doute, il vaut mieux faire tous les appels a ce systeme dans un
seul et meme thread : meme si la fenetre est toujours présente et que
le thread de calcul y fait un appel, rien ne dit qu'un autre thread de
calcul qui fait, au meme moment, un appel a une autre fenetre ne va pas
causer un probleme au sein du systeme de fenetrage.


En Qt, pour ce que j'en sais, l'appel depuis un thread d'une fonction membre
d'un objet (style Fenêtre...) qui a été créé par un autre thread est
considéré comme imprévisible, à proscrire donc (sauf cas particuliers
classes de synchro etc...).

--
Aurélien REGAT-BARREL

Avatar
Aurélien REGAT-BARREL
Volatile n'est vraissemblablement pas la solution d'un probleme de
multithread


Je dirais même plus : il me semble que volatile est surtout utilisé
sur des systèmes ne gérant pas plusieurs threads, et sert à indiquer
que la variable peut être modifiée par une interruption. En gros, sous
DOS, ça pouvait peut-être servir ; sous Windows 32 bits, guère.


Apparement ma crainte est infondée alors:

class Test
{
public:
void DoSomething()
{
if ( this->DoIt )
{
}
}

bool DoIt;
};

J'ai peur que si thread1 modifie DoIt, thread2 "loupe" cette modification
lors d'un appel à DoSomething() sur la même instance, suite à une
optimisation du compilo (mise de DoIt dans un registre...).

--
Aurélien REGAT-BARREL


Avatar
Jean-Marc Bourguet
"Aurélien REGAT-BARREL" writes:

Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.


A quoi sert volatile alors ?


Dans un programme conforme (donc sans thread, je parie qu'avec des
threads ca doit etre faux)
- permet d'acceder a des variables au retour de setjmp,
- permet de regarder et de modifier des variables dans un signal

a part cela, il vaut mieux connaitre l'environnement tres bien pour
etre sur que ca marche.

Les problemes avec des volatiles qui n'assurent pas ce que l'on veut
commencent generalement avec des machines multiprocesseurs ayant une
gestion de cache aggressive (l'alpha est tres souvent cite car c'est
le premier qui a montre que ca ne tenait pas la route, je ne suis plus
les developpements pour etre sur d'autres, l'ia64 me semble un bon
candidat avec sa myrriade d'instructions de synchronisation de cache).
Sur un mono-processeur, ca doit generalement marcher assez bien.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Arnaud Debaene
Aurélien REGAT-BARREL wrote:
Apparement ma crainte est infondée alors:

class Test
{
public:
void DoSomething()
{
if ( this->DoIt )
{
}
}

bool DoIt;
};

J'ai peur que si thread1 modifie DoIt, thread2 "loupe" cette
modification lors d'un appel à DoSomething() sur la même instance,
suite à une optimisation du compilo (mise de DoIt dans un
registre...).


DoIt étant potentiellement accessible par un autre thread, le compilateur
n'a pas le droit de le cacher en registre. Il peut le faire par contre pour
une variable locale car on est certain qu'elle n'est accédée par personne
d'autre.

Arnaud

Avatar
Aurélien REGAT-BARREL
DoIt étant potentiellement accessible par un autre thread, le compilateur
n'a pas le droit de le cacher en registre. Il peut le faire par contre
pour

une variable locale car on est certain qu'elle n'est accédée par personne
d'autre.


Ah ok très bien c'est bon alors.
Merci.

--
Aurélien REGAT-BARREL

Avatar
kanze
Olivier Azeau wrote:
wrote:
Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments.


La question qui est derriere cela c'est : le systeme de
fenetrage est-il thread-safe ?


Si il ne l'est pas, il est hors question de l'utiliser dans un
environement multi-threaded. Mais il le sont prèsque tous.

En revanche, ce n'est pas parce que le système est thread safe
que tu peux faire n'importe quoi. En général, il y aurait un
thread qui gère les évenemments du GUI, et tu n'as pas droit à
faire grand chose avec les composants graphique en dehors de ce
thread. Et puis, il y aurait des mechanismes pour générer des
évenements dans ce thread.

Dans le doute, il vaut mieux faire tous les appels a ce
systeme dans un seul et meme thread : meme si la fenetre est
toujours présente et que le thread de calcul y fait un appel,
rien ne dit qu'un autre thread de calcul qui fait, au meme
moment, un appel a une autre fenetre ne va pas causer un
probleme au sein du systeme de fenetrage.


Tout à fait. Même plus, rien ne dit qu'il y aurait pas un
évenemment de découvrement de la fenêtre précisement à ce
moment-là. Et wrapper tout avec des mutex, ce n'est pas en
général une bonne idée.

Bien souvent (en tt cas dans les applis que je connais), quand
on a besoin d'integrer un composant tiers, systeme de
fenetrage ou autre, on cantonne tous les appels dans un seul
thread pour ne pas risquer ce genre de mésaventure.


Quand il s'agit d'une interface graphique, en général, c'est
l'interface graphique qui gère son propre thread, parce qu'il
faut qu'il puisse réagir rapidement aux évenemments.

Je suppose qu'il y a un nom (que je ne connais pas) a cet
idiome.


Il y a quelque chose du genre supplier/consumer, mais je ne suis
pas sûr si ça couvre exactement ce auquel tu penses.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Aurélien REGAT-BARREL wrote:
Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments. Pour mettre à jour un barre
d'avancement, par exemple, on envoie un évenement au thread
gestionnaire des événements, qui s'en occupe. L'alternatif,
c'est (en supposant un MVC) que le thread de calcul fasse la
mise à jour du modèle, puis envoie un événemment au thread
d'affichage pour l'informer qu'il y a eu un changement.


C'est exactement cela. Le probleme est dans l'envoie de
l'évènement. Pour cela, je dois spécifier la fenêtre
destinatrice. Mon probleme : si par la suite cette fenêtre est
détruite, l'envoie d'évènement fait tout crasher (pointeur
null reference).


Est-ce qu'il ne serait pas possible d'envoyer un identificateur,
et de faire le mapping identificateur->objet graphique qu'une
fois l'événement arrivé dans le thread d'affichage ?

Qt aurait pu être mieux fouttu sur ce point mais c'est pas le
cas. Alors j'ai pris mes précautions, ça ne se produira plus,
mais bon j'aime pas avoir ce genre de sournoiserie qui
traine...

Ce qui m'est imposé par Qt, c'est d'utiliser un QObject* comme
destinataire de l'évènement. Le probleme c'est que si ce
QObject* est détruit par la suite, boum badaboum.

Solution : j'utilise un QGuardedPtr<QObject>, qui a une
particularité : être automatiquement mis à NULL si l'objet
pointé est détruit. Ca marche très bien.
Ma question : le fait que ce QGuardedPtr soit mis à NULL par
un autre thread, je me demande si une optimisation du compilo
ne risque pas de lui faire louper l'info.


Le compilateur, ou même simplement le hardware.

C'est un peu comme si on avait ça:

std::shared_ptr<int> ptr( new int );

thread1:
while ( ptr != 0 ) { /* ... */ }

thread2:
/* blablabla */
ptr.reset();

sauf que apparement shared_ptr est thread safe. Mais vous
pigez ma question : le test:

while ( ptr != 0 )
fait dans thread1 ne risque-t-il pas d'être faussé par la
modif de thread2 "en parallèle" ?


Peut-être. Ici, ce n'est pas claire ce qu'il faut protéger. Si
tu fais quelque chose du genre :

while ( ptr ) {
ptr->...
}

rien n'est protégé.

Bon je réalise que rien n'empêche ptr d'être invalidé après le
test... Bref, je vais devoir faire de la synchronisation.


Tout à fait. Mais même. À un moment donné, tu vas décider que
c'est bon, que le pointeur n'est pas nul. Tu as déjà pris le
lock, évidemment, et tu construis donc l'événement et tout ce
qu'il faut, que tu envoies à Qt. Puis tu libère le lock.
Seulement, Qt a peut-être déjà d'autres évenemments dans sa
queue. Et qu'est-ce qui se passe si l'évenemment avant le tien
provoque la destruction de ton cible ?

Dans le premier cas, il y a deux solutions :

1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.

Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.


Y'a plus simple dans mon cas : un QGuardedPtr est
automatiquement mis à NULL par Qt une fois l'objet pointé
déréférencé.


Attention : il est impossible d'obtenir un pointeur brut d'un
boost::weak_ptr. Tout au plus peut-on obtenir un
boost::shared_ptr (qui empèche du coup le delete, parce qu'il
est compté). Si tu as un boost::weak_ptr, et tu veux y accéder,
le protocol est d'en tirer un shared_ptr, vérifier que le
shared_ptr est bon, et accéder à travers le shared_ptr. Tant que
tu as le shared_ptr, évidemment, l'objet ne serait pas deleté.

Et ce n'est pas évident pour le faire marcher
correctement -- en gros, je dirais que l'utilisation du
pointeur en tant que pointeur ne doit avoir lieu que
dans le thread d'affichage, qui doit aussi être le seul
thread qui pourrait détruire l'objet d'affichage.

Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui
gère la destruction ; il peuvent donc bien s'arranger
pour que la destruction n'a pas lieu à un moment
importun. Ici, c'est Qt qui le gère, et il faut être
certain que Qt ne le fait pas quand on a déjà extrait le
pointeur brut, mais qu'on n'en a pas encore servi. AMHA,
la solution qui s'impose, c'est que le seul thread qui
se sert du pointeur brut, c'est le même thread que celui
qui pourrait éventuellement faire un delete sur l'objet.


Y'a pas de delete. Dans mon cas ça s'utilise comme ça:

void MainWnd::OnDoSomething()
{
DoSomethingDialog dialog( this );
dialog.exec();
}

et le exec() lance un thread et affiche une barre de
progression qui suit l'évolution du traitement.

Si le programmeur n'a pas géré la fermeture de
DoSomethingDialog de manière à interrompre le thread / ou
attendre qu'il se termine, exec() rend la main, on sort de
OnDoSomething() et le dialog est détruit, mais le thread lui
continue d'envoyer un feedback... sur &dialog, et...

2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.


Je suis obligé de passer par un QObject* pour envoyer un
évènement, car c'est un mécanisme interne à Qt. Le QObject
indiqué reçoit alors l'évènement via une fonction virtuelle
dédiée.

Dans le deuxième cas, il faut bien que le thread de calcul
ait accès à l'objet d'affichage.


Ca c'est gênant, ça me fait une dépendance croisée
FenetreAffichage <-> ThreadCalcul.


Ça arrive malheureusement souvent dans le cas des listener.

Dans ce cas-là, il faut impérativement 1) qu'il soit
observeur, pour que le pointeur soit mis à null lors d'un
delete, et 2) qu'il detient un lock aussi longtemps qu'il a
une copie du pointeur, et que ce même lock protège la
deletion (c-à-d qu'autant qu'il a le lock, on ne peut pas
deleter).


Je ne peux empêcher le delete/destruction, ce serait fâcheux.
Dans ce cas ce n'est plus le thread principal qui controle le
thread de calcul, mais le 2° qui peut bloquer le 1°, et alors
la message pump l'est aussi, et donc toute l'appli aussi, y
compris l'envoie d'évènements, plus rien ne fonctionne... Je
préfère l'autre sens : le thread d'affichage tue le process
avant de fermer le dialog, ou plus proprement lui dit de
s'arrêter (ce qui est logique même si je n'avais pas ce
probleme).

Pour 2), j'aime plutôt l'idée d'un pointeur intelligent dont
le constructeur aquiert le lock, et le dernier destructeur
le libère. On peut se servir de boost::smart_ptr ici, à
condition d'acquérir le lock avant de créer le pointeur, et
que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère
le lock (et qui ne delete rien). Évidemment, il faut que le
code qui effectue le delete de l'objet prend le même lock.
(Je suis assez scéptique sur la prise d'un lock dans un
destructeur. Mais à la rigueur, comme première action dans
le destructeur de la classe la plus dérivée. Ce qu'il faut
tenir en compte, c'est qu'entre le moment que la décision a
été prise de detruire l'objet et le moment que le thread
d'affichage prend le lock, le thread de calcul peut
interrompre, obtenir un pointeur à l'objet d'affichage, et
s'en servir. Or, si on est déjà dans le destructeur...)


Oui je comprend. Mais je vais faire plus simple. Au lieu de
passer un QObject* simple à mon thread de calcul (pour envoyer
le feedback), je vais lui donner une structure "concurrent
access safe" qui permet de savoir si l'arrêt du traitement a
été demandé. Si c'est le cas il n'envoie plus d'évènements et
se termine


Idéalement, c'est à peu près comme ça que je travaillerais. Sauf
que le fenêtre ne se fermera réelement que sur la commande du
thread de calcul -- la commande de fermature de la fenêtre se
convertit donc en commande d'arrêter le calcul, et l'arrêt du
calcul provoque l'envoie d'une fermature de la fenêtre.

Sinon, on joue toujours à chasser/croiser. Le thread de calcul
envoie un évenemment de mise à jour à l'affichage, mais un
évennement qui le précède dans la queue d'évenements provoque la
disparition de la fenêtre.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


1 2 3