multithreading delphi sockets indy

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;