Indy 10 IdTCPClient lecture de données en utilisant un thread séparé?
-
23-08-2019 - |
Question
Question: Qu'est-ce que je cherche est le le plus typique ou les meilleures pratiques façon d'utiliser un thread séparé pour recevoir des données au moyen d'un IdTCPClient dans Indy 10.
Contexte: Le code ci-dessous est un échantillon de ce que je suis en train de faire avec les parties de traitement de données réelles supprimées pour plus de clarté. L'idée du fil est de recevoir toutes les données (taille variable avec un en-tête déclarant le reste de la longueur du message) et ensuite l'analyser (C'est ce que la procédure HandleData fait) et déclencher un gestionnaire d'événements en fonction de la commande.
Le TIdIOHandlerSocket est passé sur le fil par l'application principale qui écrit aussi des données sur la prise et que lorsque cela est nécessaire.
TScktReceiveThread = class(TThread)
private
{ Private declarations }
procedure HandleData;
protected
procedure Execute; override;
public
FSocket: TIdIOHandlerSocket;
constructor Create(CreateSuspended: boolean);
end;
procedure TScktReceiveThread.Execute;
var
FixedHeader: TBytes;
begin
Assert(FSocket <> nil, 'You must assign the connected socket to the receiving thread');
SetLength(FixedHeader, 2);
while not Terminated do
begin
if not FSocket.Connected then
Suspend
else
begin
FSocket.CheckForDataOnSource(10);
if not FSocket.InputBufferIsEmpty then
begin
FSocket.ReadBytes(FixedHeader, SizeOf(FixedHeader), false);
// Removed the rest of the reading and parsing code for clarity
Synchronize(HandleData);
end;
end;
end;
end;
En tant que préfixe, j'ai utilisé une autre question StackOverflow qui traite des composants du serveur d'Indy: " Delphi 2009, Indy 10, TIdTCPServer.OnExecute, comment saisir tous les octets dans le InputBuffer " pour obtenir la base de ce que j'ai jusqu'à présent.
Merci pour toute aide!
La solution
Si vous voulez éviter les frais généraux imposés par la création de classes de fil pour chaque échange de données client-serveur, vous pouvez créer une classe de filetage motiles comme décrit dans
http: //delphidicas.blogspot. com / 2008/08 / anonymes-méthodes quand devraient-ils-be.html
J'ai eu le même problème il y a quelques jours et je me écrit une classe TMotileThreading qui a des fonctions statiques qui me permettent de créer des threads en utilisant la nouvelle fonctionnalité de méthode anonyme de D2009. On dirait quelque chose comme ceci:
type
TExecuteFunc = reference to procedure;
TMotileThreading = class
public
class procedure Execute (Func : TExecuteFunc);
class procedure ExecuteThenCall (Func : TExecuteFunc; ThenFunc : TExecuteFunc);
end;
La deuxième procédure me permet d'effectuer une communication client-serveur comme dans votre cas et faire des choses à chaque fois que les données sont arrivées. La bonne chose à propos des méthodes anonymes est que vous pouvez utiliser les variables locales du contexte d'appel. Ainsi, une communication ressemble à quelque chose comme ceci:
var
NewData : String;
begin
TMotileThreading.ExecuteThenCall (
procedure
begin
NewData := IdTCPClient.IOHandler.Readln;
end,
procedure
begin
GUIUpdate (NewData);
end);
end;
L'exécution et la méthode ExecuteThenCall simplement créer un thread de travail, mis FreeOnTerminate true pour simplifier la gestion de la mémoire et exécuter les fonctions prévues dans le thread de travail de exécution et les procédures OnTerminate.
L'espoir qui aide.
EDIT (comme l'a demandé la mise en œuvre complète de la classe TMotileThreading)
type
TExecuteFunc = reference to procedure;
TMotileThreading = class
protected
constructor Create;
public
class procedure Execute (Func : TExecuteFunc);
class procedure ExecuteAndCall (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc;
SyncTerminateFunc : Boolean = False);
end;
TMotile = class (TThread)
private
ExecFunc : TExecuteFunc;
TerminateHandler : TExecuteFunc;
SyncTerminateHandler : Boolean;
public
constructor Create (Func : TExecuteFunc); overload;
constructor Create (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc;
SyncTerminateFunc : Boolean); overload;
procedure OnTerminateHandler (Sender : TObject);
procedure Execute; override;
end;
implementation
constructor TMotileThreading.Create;
begin
Assert (False, 'Class TMotileThreading shouldn''t be used as an instance');
end;
class procedure TMotileThreading.Execute (Func : TExecuteFunc);
begin
TMotile.Create (Func);
end;
class procedure TMotileThreading.ExecuteAndCall (Func : TExecuteFunc;
OnTerminateFunc : TExecuteFunc;
SyncTerminateFunc : Boolean = False);
begin
TMotile.Create (Func, OnTerminateFunc, SyncTerminateFunc);
end;
constructor TMotile.Create (Func : TExecuteFunc);
begin
inherited Create (True);
ExecFunc := Func;
TerminateHandler := nil;
FreeOnTerminate := True;
Resume;
end;
constructor TMotile.Create (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc;
SyncTerminateFunc : Boolean);
begin
inherited Create (True);
ExecFunc := Func;
TerminateHandler := OnTerminateFunc;
SyncTerminateHandler := SyncTerminateFunc;
OnTerminate := OnTerminateHandler;
FreeOnTerminate := True;
Resume;
end;
procedure TMotile.Execute;
begin
ExecFunc;
end;
procedure TMotile.OnTerminateHandler (Sender : TObject);
begin
if Assigned (TerminateHandler) then
if SyncTerminateHandler then
Synchronize (procedure
begin
TerminateHandler;
end)
else
TerminateHandler;
end;
Autres conseils
Vous êtes sur la bonne voie. Indy est destiné à utiliser comme ça. Il utilise prises blocage , de sorte que l'appel ReadBytes
ne retourne pas jusqu'à ce qu'il soit lu ce que vous avez demandé. Voilà qui contraste avec les prises non-blocage, où un appel peut revenir au début,
Indy est conçu dans l'espoir que les objets socket ont leurs propres fils (ou fibres). Indy est livré avec TIdAntifreeze
pour les gens qui veulent faire glisser et déposer les composants de prise sur leurs formes et modules de données et utiliser les composants Indy du fil de l'interface graphique principale, mais qui est généralement pas une bonne idée si vous pouvez l'éviter.
Étant donné que votre fils ne peut pas fonctionner sans FSocket
être affecté, je vous conseille de recevoir simplement cette valeur dans le constructeur de la classe. Affirmez dans le constructeur si elle est non affecté. De plus, il est un erreur pour créer votre fil non suspendu, alors pourquoi même donner l'option? (Si le fil ne se crée pas suspendu, il commencera à fonctionner, vérifier si FSocket
est affecté, et échouent parce que le fil n'a pas obtenu la création d'attribuer ce champ encore.)