IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Synchronize non bloquant avec D5 devenu bloquant sous D7 ?!!


Sujet :

Langage Delphi

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut Synchronize non bloquant avec D5 devenu bloquant sous D7 ?!!
    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 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
     
    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...



    Merci d'avance de votre aide.

    Balises code ajouté par Laurent Dardenne

  2. #2
    Membre expérimenté

    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    685
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 685
    Points : 1 608
    Points
    1 608
    Par défaut
    D'abord, effectivement le système de threading a changé depuis D5 :
    http://community.borland.com/article/0,1410,27655,00.html

    La vérification des procédures à synchroniser est faite dans TApplication.Idle and TApplication.WndProc. Etant donné que ton long traitement bloque le défilage des messages, je pense que CheckSynchronize n'est plus appellé (sous réserve que j'ai bien compris ton problème exact...). Peut être qu'un appel plus ou moins régulier à Application.ProcessMessages dans la procédure de traitement long pourrait résoudre le problème, en forçant la lecture des messages et l'appel à CheckSynchronize ?
    "It's more fun to compute !"

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Merci pour ta réponse !

    Je n'aime pas vraiment l'idée de faire des appels à 'ProcessMessages' dans la thread principale, je trouve cela vraiment pas très propre (à vrai dire je bannis habituellement l'utilisation de ProcessMessages ...), cela me forcerait à désactiver la form en cours d'utilisation et de plus je n'ai pas toujours la possibilité de faire des appels à ProcessMessages (certains appels de fonctions non décomposables prennent plusieurs secondes). En fait il s'agissait de l'ancienne implémentation que j'ai justement modifié car celle-ci entraînait un nombre incroyable de bug dans l'application (à cause des évènements comme 'OnTimer', 'OnClick'... éxécutés au milieu d'un long traitement).

    Je soupçonne que dans Delphi5 l'appel à l'équivalent de "CheckSynchronize" sous Delphi7 est fait beaucoup plus fréquement que dans Delphi7, à partir d'appels de fonctions diverses de la VCL puisque si je me contente de faire un sleep( XX) dans les 2 cas c bloquant, mais lorsqu'il s'agit d'un traitement divers qui appelle des fonctions VCL ou autres alors le "Synchronize" passe souvent sous Delphi5 (et plus du tout sous Delphi7)...

    C'est assez embêtant de constater qu'en changeant de version de Delphi l'application se comporte différement et je ne vois pas comment je peux remédier au problème (je ne vais quand meme pas modifié les sources VCL de Delphi7 pour que mon appli se comporte comme sous Delphi 5, de toute façon j'en suis capable)

  4. #4
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    up

  5. #5
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 152
    Points
    10 152
    Par défaut
    Ne pourrais-tu pas envisager de passer ton traitement long dans un thread et de remettre l'affichage du splash screen dans le thread principal (là où il doit être d'ailleurs). Je pense que ça te faciliterait énormément la tâche
    sjrd, ancien rédacteur/modérateur Delphi.
    Auteur de Scala.js, le compilateur de Scala vers JavaScript, et directeur technique du Scala Center à l'EPFL.
    Découvrez Mes tutoriels.

  6. #6
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    C vrai que l'implémentation à la base est un peu mal fichu comme je l'avais dis dans mon premier post mais 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 threads, de plus il y a des cas où ce n'est vraiment pas évident du tout à implémenter...

    Entre temps j'ai essayé de comprendre le pourquoi de mon problème en regardant les sources VCL et notamment l'implémentation de 'Synchronize' et la gestion entière de synchronisation interthreads. Les différences entre Delphi5 et Delphi7 sont les suivantes :

    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').
    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 de la boucle de traitement des messages donc)

    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...). En tout cas ça marche à peu près ainsi, même si dans certains cas le splashscreen reste bloqué cela reste sastifaisant.

Discussions similaires

  1. Réponses: 2
    Dernier message: 22/05/2014, 16h53
  2. Réponses: 1
    Dernier message: 17/12/2009, 11h59
  3. Génération classe mapping avec synchronizer non conforme
    Par floralys dans le forum Hibernate
    Réponses: 0
    Dernier message: 16/09/2009, 17h28
  4. problème bloquant avec "SELECT LAST_INSERT_ID()"
    Par r2d2abc dans le forum JDBC
    Réponses: 15
    Dernier message: 21/04/2009, 01h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo