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 :

Problèmes avec TIdFtp et l'implémentation en thread


Sujet :

Langage Delphi

  1. #1
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2010
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 4
    Points : 1
    Points
    1
    Par défaut Problèmes avec TIdFtp et l'implémentation en thread
    Bonjour à tous,

    Je voudrais vous soumettre un problème auquel je suis confronté depuis une semaine.

    Je suis en train de concevoir un programmes qui inclut un module de communication FTP.
    J'ai décidé d'utiliser le composant TIdFtp de Indy 10 (que j'ai mis à jour vers la dernière version à partir du site officiel).

    Ce composant offre la possibilité de récupérer et parser automatiquement le contenu d'un serveur via la méthode List.
    Toutefois, utiliser List dans le thread principal a le principal désavantage de bloquer le processus, ce qui est quelque peu gênant vu que certains de mes serveurs de test peuvent prendre jusqu'à 5 minutes pour répondre à LIST.

    C'est pourquoi je voulais mettre l'appel à List dans un thread séparé. Je manque de connaissance concernant les threads (c'est en fait la première fois que j'utilise des threads dans un projet majeur), et de ce fait je ne suis pas sûr que mon implémentation de List dans ce contexte soit appropriée. J'ai également tenté d'utiliser TIdAntiFreese, mais cela ne semble pas fonctionner.

    Le problème avec ce thread est que, chaque fois que je démarre le thread à partir de la procédure mère, j'obtiens une Violation d'accès (lecture à l'adresse "00000004").

    Ce qui suit est le code principal de mon thread, qui a pour seul but d'effectuer List.

    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
     
    constructor TFtpListerThread.Create(CreateSuspended: Boolean);
    begin
      inherited Create(CreateSuspended);
      FreeOnTerminate := false;
      Priority := tpNormal;
    end;
     
    procedure TFtpListerThread.DoAction;
    begin
      if (FtpTrafficBox.teststatus = 0) then     // to make sure that the thread does only one execution
        begin
          try
            FtpTrafficForm.FtpModule.List(nil, '', true);     // the most important line of this thread
          except
            FtpTrafficBox.teststatus := 2;     // teststatus evaluated to 2 in case of failure
          end;
          if (FtpTrafficBox.teststatus = 0) then
            FtpTrafficBox.teststatus := 1;     // on success, evaluated to 1
        end;
    end;
     
    procedure TFtpListerThread.Execute;
    begin
      while not Terminated do
        Synchronize(DoAction);
    end;
    Vu que le composant TIdFtp n'est pas hébergé dans ce thread, j'ai utilisé Synchronize pour éviter les problèmes d'interblocage (ou du moins, c'est supposé l'éviter).
    A ce que je sache, il n'y a aucun moyen de retourner facilement une valeur à la sortie du thread, j'ai donc utilisé une réponse par variable globale pour indiquer au processus appelant que List a été effectué. Je suis certain qu'il y a de meilleurs moyens de faire ça ; je suis ouvert à toute suggestion.

    La procédure appelante du thread principal est la suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
          teststatus := 0;     // Re-initializes the global variable
          FtpListerThread.Resume;     // Starts thread
          while (teststatus = 0) do
            begin
              sleep(10);
              Application.ProcessMessages;     // Keep process messages while it awaits for the end of the thread (not very elegant, I know)
            end;
          FtpListerThread.Suspend;     // Stops the thread when List has finished
          if (teststatus = 2) then
            begin
               // Procedures in case of failure
            end;
    Bien que le debuggueur n'ait pas été très clair sur l'origine du crash, je suis pratiquement sûr que cela se produit quand le thread tente d'effectuer List.
    De plus lorsque je tente d'éviter l'appel au thread en mettant List dans une structure try-except, cela est effectué sans aucun problème, excepté pour le problème de lag que je souhaitais éviter avec le thread.
    De nombreuses personnes ont reporté des problèmes similaires qui ont été corrigés dans une mise à jour de Indy 10 (maj qui concernait une modification d'un Integer vers un Int64 ou qq chose comme ça). Mettre à jour le Indy 10 original inclus dans Delphi n'a pas résolu le problème.

    Je pense que tout a été dit. J'espère que vous serez en mesure de me donner un coup de main dans cette histoire.
    Si vous avez besoin d'informations complémentaires, ou si vous voulez me taper sur les doigts pour mon code (dégeu ?), n'hésitez surtout pas.

    Merci d'avance pour votre aide.

    Steph.

  2. #2
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 745
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 745
    Points : 13 306
    Points
    13 306
    Par défaut
    Comme tu as fait, le thread ne sert à rien, si ce n'est introduire des erreurs
    Synchronize fait que la fonction sera de toute façon exécutée dans la tâche principale.

    Il faut plutôt créer dynamiquement le composant FTP dans OnExecute du Thread, remplir une liste propre au thread et la récupérer dans l'évenement OnTerminate.

  3. #3
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2010
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 4
    Points : 1
    Points
    1
    Par défaut
    Bonjour Andnotor et merci pour ta réponse.

    A vrai dire, l'idée que ce genre de thread pouvait s'avérer inutile m'a traversé l'esprit, mais le fait est que j'ai créé un autre thread qui s'occupe de se connecter au serveur sans bloquer l'application, notamment lorsque l'hôte n'existe pas.
    En testant avec et sans thread, j'ai noté que la version avec thread ne bloquait pas l'application, alors que son code est rigoureusement identique (Connect remplace List).

    S'agirait-il d'un effet placebo ?

  4. #4
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 745
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 745
    Points : 13 306
    Points
    13 306
    Par défaut
    Le thread n'est pas inutile. C'est le fait d'utiliser Synchronize qui le rend inutile

    Voilà un exemple avec création dynamique du TIdFTP et récupération du listing dans l'événement OnTerminate:

    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
    90
    91
    92
    93
    94
    95
    unit Unit1;
     
    interface
     
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, idFTP, StdCtrls;
     
    type
      TForm1 = class(TForm)
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
      public
        procedure OnListComplete(Sender :TObject);
      end;
     
      TFTPListThread = class(TThread)
      private
        URL      :string;
        User     :string;
        Password :string;
        FList    :TStringList;
        FResult  :boolean;
      protected
        procedure Execute; override;
      public
        property List :TStringList read FList;
        property Result :boolean read FResult;
        Constructor Create(aURL, aUser, aPassword :string; aOnTerminate :TNotifyEvent);
        destructor Destroy; override;
      end;
     
    var
      Form1: TForm1;
     
    implementation
     
    {$R *.dfm}
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      TFTPListThread.Create('ftp.domain.com', 'user', 'password', OnListComplete);
    end;
     
    procedure TForm1.OnListComplete(Sender: TObject);
    begin
      with TFTPListThread(Sender) do
        if Result
        then Memo1.Lines.Assign(List)
        else MessageDlg('Erreur', mtError, [mbOk], 0);
    end;
     
    { TFTPListThread }
     
    constructor TFTPListThread.Create(aURL, aUser, aPassword :string; aOnTerminate :TNotifyEvent);
    begin
      inherited Create(FALSE);
     
      URL         := aURL;
      User        := aUser;
      Password    := aPassword;
      OnTerminate := aOnTerminate;
     
      FList := TStringList.Create;
      FreeOnTerminate := TRUE;
    end;
     
    destructor TFTPListThread.Destroy;
    begin
      FList.Free;
      inherited;
    end;
     
    procedure TFTPListThread.Execute;
    begin
      with TIdFtp.Create(nil) do
      begin
        try
          Username := User;
          Password := Self.Password;
          Host     := URL;
     
          Connect;
          List(FList);
     
          FResult := TRUE;
        except
          FResult := FALSE;
        end;
     
        Free;
      end;
    end;
     
    end.

  5. #5
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2010
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 4
    Points : 1
    Points
    1
    Par défaut
    Ok merci Andnotor, je teste tout ça et je te tiens au courant.

  6. #6
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2010
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 4
    Points : 1
    Points
    1
    Par défaut
    A vrai dire, entretemps, j'ai trouvé d'où venait le bug. Il s'agissait d'un problème lié à ces deux lignes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    var
      FtpListerThread: TFtpListerThread;
    Bien que je ne me souvienne pas pourquoi je les ai rajoutées (je devais être fatigué), j'avoue ne pas comprendre pourquoi elles font planter le thread.

    J'avais posé ce même problème sur le site d'Embarcadero. Pour ceux que ça intéresse, voici le lien.

    Merci encore !

  7. #7
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Désolé de faire remonter ce fil, j'ai fait une petite recherche sur le forum qui m'a mené ici...

    @Andnotor: je suis sur un projet où je dois télécharger simultanément plusieurs fichiers et faire du traitement dessus -> est-ce que ta méthode est valable avec plusieurs threads travaillant en même temps ? Je pense en particulier à l'appel à la procédure "OnListComplete", que se passe-t'il si plusieurs threads l'appellent en même temps, toujours pas besoin de synchronisation ? (dans mon cas je veux afficher l'avancement des opérations dans un memo "Log" et enregistrer des données dans une BDD SQLite...)

  8. #8
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 745
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 745
    Points : 13 306
    Points
    13 306
    Par défaut
    L'appel à OnTerminate se fait (en interne) par Synchronize. Il n'y a donc aucun soucis d'accès concurrent

  9. #9
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Ah merci, j'allais justement m'auto-répondre avec ceci:

    http://bloon.developpez.com/articles...tethread/#L5.2

    C'est vrai ça, on google, on google (ou dans mon cas on duckduckgo) alors que toutes les réponses sont déjà sur developpez.net !

    PS tout de même: en fait le OnTerminate est lancé avant la vraie "terminaison" du thread puisqu'on est encore capable d'accéder à l'objet à ce moment là ???

  10. #10
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 745
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 745
    Points : 13 306
    Points
    13 306
    Par défaut
    Citation Envoyé par GoustiFruit Voir le message
    PS tout de même: en fait le OnTerminate est lancé avant la vraie "terminaison" du thread puisqu'on est encore capable d'accéder à l'objet à ce moment là ???
    Je dirais même que la durée de vie du Thread est plus longue que l'objet TThread si FreeOnTerminate est vrai
    Ne pas confondre les Threads (au niveau OS) et l'objet TThread qui n'est qu'une encapsulation de ce principe.

  11. #11
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Pendant que je t'ai sous la main :-) dans ce post tu crées un tthread avec Suspended à False mais tu lui affectes des propriétés (ici OnTerminate) après création: est-ce que c'est sans risque ? Je veux dire que l'exécution du thread est sensée démarrer aussitôt la création dans cet exemple, donc si certaines propriétés ne sont pas renseignées avant, le tthread ne va pas t'attendre ?

  12. #12
    Expert éminent sénior
    Avatar de Cl@udius
    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2006
    Messages
    4 878
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Février 2006
    Messages : 4 878
    Points : 10 008
    Points
    10 008
    Par défaut
    Salut

    L'appel de la méthode Execute ne fera pas avant d'être sorti du constructeur.
    Donc non pas de danger, on peut mettre Suspended à False.

    @+

  13. #13
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Ah tu veux dire que ce code ne s'effectue pas en 2 étapes...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    with ThreadFlux.Create('http://www.developpez.net/forums/') do
        OnTerminate := DownloadComplete;
    ... contrairement à celui-ci ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    a := ThreadFlux.Create('http://www.developpez.net/forums/');
    a.OnTerminate := DownloadComplete;
    J'en apprends tous les jours

  14. #14
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 745
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 745
    Points : 13 306
    Points
    13 306
    Par défaut
    Si, si. Ces deux codes sont identiques !

    C'est vrai que dans le sujet que tu cites, le thread est en exécution au moment de l'assignation de Onterminate. (J'ai certainement été paresseux)

    Sinon, j'utilise plutôt le principe ci-dessus, soit le passage de la méthode par paramètre au constructeur.
    Il faut savoir qu'un thread (OS) est toujours créé dans le TThread en mode suspendu, quelque soit l'état désiré. C'est la méthode AfterConstruction qui le démarre au besoin. Le constructeur reste le meilleur endroit pour les initialisations

  15. #15
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Ah zut, faut que je re-modifie mon code alors :-p

    J'ai plusieurs paramètres à initialiser avant de lancer mon thread, et j'ai besoin de créer quelques classes descendantes utilisant d'autres paramètres donc je crois que je vais créer des threads avec Suspended à True par défaut, le temps de modifier les propriétés, puis les lancer avec Resume, plutôt que de passer x paramètres dans le constructeur...

Discussions similaires

  1. Réponses: 1
    Dernier message: 08/08/2006, 15h39
  2. [TidFtp] Problème avec les FTP List Parse
    Par Philbzh dans le forum Delphi
    Réponses: 1
    Dernier message: 20/06/2006, 09h48
  3. Réponses: 11
    Dernier message: 14/02/2006, 00h26
  4. Thread--> problème avec ThreadProc
    Par stof dans le forum MFC
    Réponses: 33
    Dernier message: 08/06/2005, 13h47
  5. Réponses: 5
    Dernier message: 10/05/2005, 10h22

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