multithreading - Indy 10 IdTCPClient Reading Data usando un hilo separado?
delphi sockets (2)
Pregunta: Lo que estoy buscando es la forma más típica o de mejores prácticas de usar un hilo separado para recibir datos usando un IdTCPClient en Indy 10.
Antecedentes: el siguiente código es una muestra de lo que trato de hacer con las piezas de procesamiento de datos reales eliminadas para mayor claridad. La idea del subproceso es recibir todos los datos (tamaño variable con un encabezado que declara el resto de la longitud del mensaje) y luego analizarlos (eso es lo que hace el procedimiento HandleData) y activar un manejador de eventos según el comando.
TIdIOHandlerSocket se pasa al hilo por la aplicación principal, que también escribe datos en el zócalo cuando sea necesario.
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;
Como prefijo, he utilizado otra pregunta de StackOverflow que trata sobre los componentes de servidor de Indy: " Delphi 2009, Indy 10, TIdTCPServer.OnExecute, cómo capturar todos los bytes en InputBuffer " para obtener la base de lo que tengo hasta ahora .
¡Gracias por cualquier ayuda!
Estás en el camino correcto. Indy está destinado a ser usado así. Utiliza sockets de bloqueo , por lo que la llamada a ReadBytes
no se devuelve hasta que no se lee lo que ha pedido. Contraste eso con los zócalos sin bloqueo, donde una llamada puede regresar temprano, por lo que puede sondear o recibir notificaciones de forma asíncrona para determinar cuándo se ha completado una solicitud.
Indy está diseñado con la expectativa de que los objetos del socket tengan sus propios hilos (o fibras). Indy viene con TIdAntifreeze
para las personas que quieren arrastrar y soltar componentes de socket en sus formularios y módulos de datos y usar los componentes de Indy del hilo principal de la GUI, pero generalmente no es una buena idea si puedes evitarlo.
Como su hilo no puede funcionar sin FSocket
asignado, le aconsejo que simplemente reciba ese valor en el constructor de la clase. Afirma en el constructor si no está asignado. Además, crear un hilo no suspendido es un error , entonces ¿por qué incluso dar la opción? (Si el hilo no se crea suspendido, entonces comenzará a ejecutarse, verificará si FSocket
está asignado y fallará porque el hilo de creación no ha llegado a asignar ese campo todavía).
Si desea evitar la sobrecarga impuesta creando clases de subprocesos para cada intercambio de datos cliente-servidor, puede crear una clase de subprocesamiento móvil como se describe en
http://delphidicas.blogspot.com/2008/08/anonymous-methods-when-should-they-be.html
Tuve el mismo problema hace unos días y me acaba de escribir una clase TMotileThreading que tiene funciones estáticas que me permiten crear subprocesos utilizando la nueva función de método anónimo de D2009. Se ve algo como esto:
type
TExecuteFunc = reference to procedure;
TMotileThreading = class
public
class procedure Execute (Func : TExecuteFunc);
class procedure ExecuteThenCall (Func : TExecuteFunc; ThenFunc : TExecuteFunc);
end;
El segundo procedimiento me permite realizar una comunicación cliente-servidor como en su caso y hacer algunas cosas cada vez que los datos han llegado. Lo bueno de los métodos anónimos es que puede usar las variables locales del contexto de llamada. Entonces una comunicación se ve así:
var
NewData : String;
begin
TMotileThreading.ExecuteThenCall (
procedure
begin
NewData := IdTCPClient.IOHandler.Readln;
end,
procedure
begin
GUIUpdate (NewData);
end);
end;
El método Execute y ExecuteThenCall simplemente crea un hilo de trabajo, configura FreeOnTerminate en verdadero para simplificar la gestión de la memoria y ejecutar las funciones proporcionadas en los procedimientos de Ejecución y Terminación del subproceso de trabajo.
Espero que ayude.
EDITAR (como solicitó la implementación completa de la clase 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;