Appel à "Synchronize" qui bloque sous Delphi7 et pas sous Delphi5 ???! - Delphi/Pascal - Programmation
Marsh Posté le 31-08-2005 à 19:14:14
C'est normal ce while où il y a juste des appels gérant le splash screen ? (dans l'execute du thread)
Mettre des trucs de la VCL dans des threads à part c'est un peu tordu, comme tu le dis. Je ne pige pas très bien la logique de ton code
Marsh Posté le 02-09-2005 à 00:52:50
antp a écrit : C'est normal ce while où il y a juste des appels gérant le splash screen ? (dans l'execute du thread) |
le "while true" aurait pû être remplacé par "while not Terminated" si tu préfères, c juste une thread dont l'unique but est de mettre à jour l'affichage du splashscreen. C vrai que l'implémentation à la base est un peu mal fichue mais bon je n'ai pas le temps de tout refaire comme il faudrait à savoir gérer l'affichage dans la thread principale et faire tous les traitements longs dans d'autres thread, de plus il y a des cas où ce n'est vraiment pas évident de faire les traitements dans d'autres threads...
Entre temps j'ai trouvé le pourquoi de mon problème, j'ai regardé les sources VCL et il apparait que l'implémentation de 'Synchronize' et de la gestion entière de synchronisation interthread est différente entre Delphi5 et Delphi7 d'où cette différence de comportement entre mon appli. compilé sur les 2 versions (c kan même un peu regrettable).
En fait dans Delphi5 cette synchro se fait par un bête envoi de message 'SendMessage' sur une fenêtre créé dans l'unique but de traiter ces messages, donc lorsqu'on appel synchronise cela reviens à faire un 'SendMessage' dont le rôle est d'éxécuter la fonction passée en arg. dans 'Synchronize', du coup cette fonction va s'éxécuter dans la thread principale (cette qui détient la fenêtre de traitement de ces messages) lorsqu'elle entrera dans la boucle de traitement de message.
Dans Delphi7 c complètement différent, il y a une liste d'événements qui contiennent les appels de fonctions passées à 'Synchronize' et en réalité c'est l'appel à "CheckSynchronize" qui par un systeme comparable aux sémaphore va éxécuter les fonctions. Malheureusement les appels à "CheckSynchronize" se font dans le 'TApplication.OnIdle' et à kkpart dans le 'WndProc', ce qui ne lui assure pas d'être aussi souvent éxécuté un message envoyé depuis une autre thread par SendMessage. En effet j'ai l'impression que ce genre de message est éxécuté dès lors qu'on appel 'PeekMessage' ou 'DispatchMessage' ou ce genre de chose, ce qui ne dois pas arriver seulement dans la boucle principale vu comment se comporte mon application sous Delphi5 (le splashscreen est MAJ régulièrement durant un long traitement et sans appels à 'ProcessMessages').
Une solution simple consiste à simuler ce comportement de Delphi5 sous Delphi7 en envoyant un message dont le but est l'éxécution de la fonction que je veux "synchroniser" dans la thread principale, comme dans mon cas il ne s'agit que d'un update cela revient à appeler "UpdateWindow" en ayant pris soin de récupérer le handle de mon splash de manière "threadsafe", ainsi je n'appele aucune fonction VCL, cela devrait être "threadsafe" (je l'espère...)
Marsh Posté le 02-09-2005 à 00:58:40
ZZZzzz a écrit : le "while true" aurait pû être remplacé par "while not Terminated" si tu préfères, c juste une thread dont l'unique but est de mettre à jour l'affichage du splashscreen. |
C'est pas logique de mettre ça dans le thread. Ce genre de boucle sert justement d'artifice quand on n'a pas de thread
J'ai l'impression que c'était quand même un gros coup de bol que ça fonctionne dans Delphi 5, vu l'utilisation un peu tordue que tu as faite de tout ça
Marsh Posté le 02-09-2005 à 01:15:43
antp a écrit : C'est pas logique de mettre ça dans le thread. Ce genre de boucle sert justement d'artifice quand on n'a pas de thread |
Le développement ça ne dois jamais être du bol , enfin je vois ce que tu veux dire et d'ailleurs moi aussi je ne comprend pas bien ce qu'il se passe. C vrai que c tordu mais en même temps le code était thread safe, et n'était pas bloquant... même si je ne comprends pas pourquoi. En fait je suppose des choses, mais pour être sûr il me faudrait savoir dans quel cas précis un message envoyé par 'SendMessage' depuis une autre thread est éxécutée, et voir si ces cas se répètent régulièrement dans divers appels aux fonctions de la VCL (hors e la boucle de traitement des messages donc)
Marsh Posté le 29-08-2005 à 15:32:05
Bonjour à tous.
Je travaillais jusqu'à présent sous Delphi 5 notre société a décidé de passer à Delphi7 pour certaines raisons (le .NET ne nous sert pas).
Nous travaillons sur un projet qui consiste en une suite d'applications qui travaillent avec une SGBD. Par moment nous sommes ammenés à effectuer un traitement assez important qui peut durer plusieurs secondes voir minutes, dans ce cas et afin que l'utilisateur ait un retour visuel du traitement nous affichons un "splashscreen" qui indique qu'un traitement est en cours. Ce splashscreen affiche une animation censée indiquer grosso modo que l'application n'est pas planté mais fait du traitement, de plus son affichage est mise à jour automatiquement de telle sorte que si une fenêtre passe par dessus à un moment donnée le splashscreen se redessine et reset ainsi toujours visible durant le temps du traitement.
Le problème est le suivant, depuis que j'ai passé le projet sous Delphi7 le splashscreen reste figé (plus d'animation et celui-ci ne se redessine plus).
Pour illustrer mes propos par du code voici un exemple d'utilisation du splashscreen :
procedure DoWork;
var
MySplashScreen: TMySplashScreen;
begin
MySplashScreen:= TMySplashScreen.Create;
try
// Affichage SplashScreen (met visible à 'true')
// 'TMySplashScreen' hérite de 'TForm'...
MySplashScreen.Show;
try
// Gros Traitement qui dure plusieurs secondes
DoSomethingLong;
finally
// On oubli pas de retirer le SplashScreen
MySplashScreen.Hide;
end;
finally
// On oubli pas de libérer le SplashScreen
MeSplashScreen.Free;
end;
end;
// Voici maintenant une partie de l'implémentation du SplashScreen, celui contient une instance
// de 'TThreadSplashScreen' qui est créé à la création du splashscreen, cette instance démarre
// une thread à sa création qui se charge de mettre à jour l'affichage du splashscreen :
// Appelé en interne par notre thread pour MAJ l'affichage,
// je l'ai décomposé dans une autre fonction afin de la synchroniser car
// des appels à des méthodes et propriétés d'objets VCL non 'threadsafe' sont effectuées
procedure TThreadSplashScreen.MyUpdate;
begin
if ( MySplashScreen = nil ) then exit;
if ( MySplashScreen.Visible ) then begin
// MAJ Animation
MySplashScreen.DoAnimate;
// MAJ splashscreen
MySplashScreen.Update;
// MAJ fiche pere
if ( Assigned(MySplashScreen.Owner) ) and ( MySplashScreen.Owner is TForm ) and
( TForm(MySplashScreen.Owner).Visible ) then TForm(MySplashScreen.Owner).Update;
end;
// s'il s'agit d'un affichage temporisé (type messagedialog), on vérifie si le temps d'affichage est écoulé
if ( MySplashScreen.AfficheTime > 0 ) then begin
// si la temporisation a été écoulée on cache le splashscreen et on réinitialise AfficheTime à 0
if ( TimeStampToMSecs( DateTimeToTimeStamp( Time ) ) > ( MySplashScreen.BeginAfficheTime + MySplashScreen.AfficheTime ) ) then begin
MySplashScreen.AfficheTime:= 0;
MySplashScreen.Hide;
end;
end;
end;
procedure TThreadSplashScreen.Execute;
begin
{ Placez le code du thread ici}
while true do begin
// on fait une temporisation correpondant au taux de rafraîchissement de l'affichage
// cela permet aussi de laisser du temps CPU aux autres threads pour s'exécuter
// (encore heureux pour un affichage de splashscreen censé indiquer un traitement en cours...)
Sleep( 1000 div (MySplashScreen.RefreshRate) );
// On teste si on doit quitter la thread
if Terminated then EndThread( 0 );
// On synchronise la MAJ de l'affichage : au final j'ai du mal à comprendre
// en quoi cela est différent de faire un timer dans le splashscreen qui
// appelerait 'MyUpdate' et pourtant à l'éxécution cela se comporte tout à
// fait différement (et heureusement sinon le splashscreen resterait figé).
// D'après l'aide de Delphi la méthode Synchronize attend que la thread
// principale entre dans la boucle de traitement des messages mais je n'ai
// pas l'impression que ce soit réellement le cas, je pense que la thread
// principale est stoppée dans son traitement, éxécute la fonction, et
// retourne à son traitement.
Synchronize( MyUpdate );
end;
end;
Vous noterez que j'accède à 'MySplashScreen.RefreshRate' hors du synchronize, cela n'est pas
"threadsafe" mais le risque encouru est tout à fait mesuré, j'ai décidé de laisser ainsi.
Le problème est que la fonction se bloque à l'appel à la méthode "Synchronize", ce qui explique que
mon splashscreen n'est pas mise à jour.
Vous remarquerez également mes commentaires d'époque que j'avais fait et qui explique en partie le problème
que je rencontre actuellement, en effet ce résultat me semble après tout logique :
comme ma thread principale est bloqué dans une fonction qui réalise du traitement, elle ne peut pas pendant ce temps
gérer et dispatcher les messages, c'est pourquoi mon splashscreen ne s'affiche pas, la méthode 'Synchronize' attends
que la thread principale rentre dans la boucle de traitement de message (en fait c'est un appel à 'CheckSynchronize'
sous Delphi7 qui "débloque" le Synchronize), or cet appel a lieu dans la boucle de traitement des messages.
CE que je ne comprend pas c'est comment mon splashscreen pouvait-il s'afficher correctement sous Delphi5, apparement
le mecanisme de synchronisation était différent bien que l'aide spécifiait que la fonction attendait que la thread
principale entre dans la boucle de traitement des messages ce n'était vraisemblablement pas tout à fait le cas.
L'affichage se figeait parfois légèrement mais d'une manière générale celui-ci se mettait bien à jour avec l'animation
"maison", ce qui n'est plus du tout le cas avec Delphi7.
Avec le recul je me rends compte que l'implémentation était mauvaise et qu'il aurait été plus logique de faire les
longs traitements dans une thread dédiée et de laisser l'affichage dans la thread principale mais le problème est
que tout le projet a été implmenté sur ce modèle et qu'il me faudrait faire bien trop de modifications complexes
pour revenir à une implémentation plus logique 'Le splashscreen doit être appelé à plus de 200 endroits différents
dans l'application...). Comment puis-je procéder pour laisser le traitement visuel dans la thread dédié sachant que
j'ai essayé pas mal de solutions en vain (Création du splashscreen dans la thread dédiée, appel de
'PeekMessage'/'DispatchMessage' dans la thread dédiée, etC....). Etant donné l'implémentation de "Form.pas"
(en gros toutes les fiches sont gérées dans la thread principale) il est normal que toutes ses solutions aient
échouées, et je ne vois pas quelle solution je pourrai choisir...
En attendant j'ai fait un simple splashscreen contenant un 'TAnimate' mais d'une part celui-ci ne met plus à jour son
affichage, ce qui signifie que si une fenêtre passe par dessus alors le splashscreen ne se redessine plus. D'autre
part le 'TAnimate' ne fonctionne pas très bien, l'animation se déclenche de manière aléatoire en apparence et j'ai un
peu de mal à cerner pourquoi...
Une dernière chose, depuis ce matin lorsque je compile en passant par l'IDE je me tape l'erreur
"[Fatal Error] Variants.pas(1024): Program or unit 'Variants' recursively uses itself" alors que bien évidemment
ce n'est pas le cas mais je n'arrive pas à comprendre ce qui pose ce problème. J'ai touché aux sources de Delphi pour faire
des tests mais depuis je les ai tous remis comme dans leur état d'origine .
Merci d'avance de votre aide.