2017-02-09 3 views
1

Je sais que les threads Delphi ont été discutés sur de nombreux threads. J'ai essayé de les revoir, mais je n'ai pas trouvé de réponse à ma question.Ce code de thread Delphi est-il correct?

Contexte: J'ai trouvé que la libération d'un TWebBrowser peut prendre 10+ secondes après le navigateur chargé d'Adobe Acrobat Reader DC. Je pense que c'est en train de vérifier les mises à jour ou quelque chose. C'est ennuyant quand on essaie de fermer un formulaire avec un navigateur.

Je pensais que je pourrais avoir un thread de fond libérer le navigateur. J'ai donc déplacé la variable du navigateur vers une variable globale (stockée en privé dans la partie implémentation de l'unité). Une seule de ces formes serait utilisée à la fois. Puis j'ai essayé d'avoir un fil libre en arrière-plan. Ça ne marche pas comme je m'y attendais.

exemple de code

interface 
    TMyform = class(TForm) 
    pnlBowserHolder: TPanel; 
    procedure FormDestroy(Sender: TObject); 
    procedure FormCreate(Sender: TObject); 
    private 
    //WebBrowser : TWebBrowser; <-- moved to global variable 
    public 
    { Public declarations } 
    end; 

implementation 

type 
    TBackgroundBrowserKillerThread = class(TThread) 
    public 
    procedure Execute; override; 
    end; 

var 
    WebBrowser : TWebBrowser; 
    BrowserKillerThread : TBackgroundBrowserKillerThread; 

procedure TfrmLabImageViewer.FormCreate(Sender: TObject); 
begin 
    WebBrowser := TWebBrowser.Create(Self); 
    TWinControl(WebBrowser).Parent := pnlBowserHolder; 
    WebBrowser.Align := alClient; 
end; 

procedure TfrmLabImageViewer.FormDestroy(Sender: TObject); 

begin 
    BrowserKillerThread := TBackgroundBrowserKillerThread.Create(true); 
    Application.ProcessMessages; 
    BrowserKillerThread.Execute(); 
    //WebBrowser.Free; 
end; 

procedure TBackgroundBrowserKillerThread.Execute(); 
begin 
    TWinControl(WebBrowser).Parent := nil; 
    FreeAndNil(WebBrowser); 
    self.FreeOnTerminate := true; 
    BrowserKillerThread := nil; //free reference to thread, shouldn't affect ability of self to free itself (?) 
end; 

Questions:

  • Quand j'étape à travers le code FormDestroy en mode de débogage, la ligne contenant BrowserKillerThread.Execute(); prend encore 10 secondes à exécuter. J'avais pensé que cela lancerait l'autre thread et reviendrait immédiatement. Mais ce n'est pas le cas. Ma compréhension est-elle fausse de ce que .execute fait? Ou est-ce que quelque chose de drôle se passe?
  • Est-ce que ce que je fais est une mauvaise chose? J'ai lu que la VCL n'est pas thread-safe, et que l'on ne peut/ne doit pas accéder aux objets VCL de l'autre thread. J'espérais que cela ne s'appliquerait pas dans ce cas puisque je libère juste l'objet sans autre interaction prévue.
  • Si un thread est libre à la fin, je suppose que cela laisserait mon pointeur BrowserKillerThread pendiller. Alors, est-ce que c'est OK d'assigner zéro à cela comme je le fais?
  • Des suggestions sur comment faire mieux?

Merci beaucoup d'avance.

KT

+3

Cela ne marchera jamais. Vous ne pouvez pas accéder à un contrôle VCL de n'importe quel type, sauf le thread principal, et vous ne pouvez pas le créer dans un thread et le détruire dans un autre. Le code que vous avez écrit ne change rien - le navigateur est toujours détruit par le thread principal qui le possède; vous appelez simplement ce code de votre thread (ce qui est faux, sauf si vous le faites via Synchronize). –

+4

En outre ... Vous souhaiterez peut-être revoir tous ces threads. Je parie que vous n'avez pas vu un seul exemple ayant la méthode d'exécution appelée de l'extérieur. Pourquoi? Car alors le code de la méthode execute s'exécuterait dans le contexte du thread qui l'appelle. –

+2

Je ne reçois pas le délai de fermeture de l'application que vous décrivez en utilisant TWebBrower + v.15.023 de Acro Reader DC. Pouvez-vous donner un exemple d'URL qui provoque le retard pour vous? – MartynA

Répondre

8

est ma mauvaise compréhension de ce que .Execute ??-t

Oui. Ce n'est pas comme ça que vous faites du filetage. Généralement, vous devez d'abord décider si vous devez faire quoi que ce soit avec le fil après l'avoir initialement lancé. Si c'est le cas, gardez une référence, n'utilisez pas FreeOnTerminate, et prenez soin de la destruction du thread vous-même après qu'elle se soit terminée. Si vous ne le faites pas, ne conservez pas de référence, définissez FreeOnTerminate et envoyez-le.

Vous ne définissez pas FreeOnTerminate dans l'exécution d'un thread, ni ne gardez une référence globale à un thread qui a cet ensemble. Vous ne démarrez pas non plus un thread en appelant sa méthode Execute, mais en le créant non suspendu ou en appelant le Start. Le simple appel au Execute ne démarre pas réellement le thread, mais exécute cette procédure dans le contexte du thread en cours, c'est pourquoi cela prend encore 10 secondes.

Votre exemple deviendrait:

procedure TfrmLabImageViewer.FormDestroy(Sender: TObject); 
    var BrowserKillerThread: TBackgroundBrowserKillerThread; 
begin 
    BrowserKillerThread := TBackgroundBrowserKillerThread.Create(true); 
    BrowserKillerThread.FreeOnTerminate := true; 
    BrowserKillerThread.Start; 
end; 

procedure TBackgroundBrowserKillerThread.Execute(); 
begin 
    TWinControl(WebBrowser).Parent := nil; // I don't think you need to nil the parent here, but probably does no harm 
    FreeAndNil(WebBrowser); 
end; 

Maintenant, le filetage est correct, mais la logique est toujours mauvaise.Comme vous l'avez déjà remarqué, vous ne devez pas modifier les objets VCL à partir de threads autres que le thread principal, cela inclut Free. Vous auriez à faire cela avec Synchronize, ce qui irait à l'encontre du but de l'enfilage.

Je pense aussi que l'idée de tuer le navigateur dans un thread parce que sinon cela prend trop de temps ne fait que lutter contre les symptômes, plutôt que la cause. Je pense qu'il serait préférable de savoir pourquoi cela prend si longtemps et de corriger cela, plutôt que d'essayer de contourner le problème de cette façon. Mais ceci est en dehors de la portée de cette question, si vous avez des problèmes avec cela, vous devriez poser une question distincte pour cette question spécifique.

+0

Commentaire génial et utile. Merci beaucoup! – kdtop

+0

J'ai joué un peu avec ça. J'utilise Delphi 10. Il ressemble à BrowserKillthread.Start devrait plutôt être BrowserKillthread.Resume. Il n'y a pas de démarrage dans ma version de Delphi. Merci encore ... – kdtop

+0

J'ai décidé de suivre les conseils de DNR, surtout avec MartynA disant qu'il n'a pas connu de retard avec la fermeture, et est allé après la cause sous-jacente. J'ai ouvert le lecteur Acrobat à partir du menu Démarrer de Windows et suis entré dans les préférences de modification. J'ai éteint tous les paramètres que je pouvais trouver qui faisaient référence à des serveurs de sécurité etc etc, et fait une exception de sécurité pour le dossier que je chargais mon .pdf de (visualisation des fichiers locaux seulement) ... Et bien sûr, le 10 secondes de retard est parti. J'écris ceci ici au cas où quelqu'un d'autre a les mêmes problèmes. Merci encore à tous pour l'aide. – kdtop