2009-02-16 24 views
5

Pregunta: Lo que estoy buscando es la más típica o mejores prácticas manera de utilizar un hilo separado para recibir datos utilizando un IdTCPClient en Indy 10.Indy 10 IdTCPClient Leyendo datos usando un hilo separado?

Antecedentes: El código siguiente es una muestra de lo que estoy tratando 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.

El 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 un prefijo, he utilizado otra pregunta StackOverflow que se ocupa de los componentes del servidor de Indy: "Delphi 2009, Indy 10, TIdTCPServer.OnExecute, how to grab all the bytes in the InputBuffer" para obtener la base de lo que tengo hasta ahora.

¡Gracias por cualquier ayuda!

Respuesta

8

Si desea evitar la sobrecarga impuesta mediante la creación de clases de hilo para todos y cada intercambio de datos cliente-servidor, se podría crear una clase de roscado móviles 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 yo sólo me escribió 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 establecer la comunicación cliente-servidor como en su caso y hacer algunas cosas cada vez que ha llegado la información. Lo bueno de los métodos anónimos es que puede usar las variables locales del contexto de llamada.Así que una comunicación es como la siguiente:

var 
    NewData : String; 
begin 
    TMotileThreading.ExecuteThenCall (
    procedure 
    begin 
     NewData := IdTCPClient.IOHandler.Readln; 
    end, 
    procedure 
    begin 
     GUIUpdate (NewData); 
    end); 
end; 

El método de ejecutar y ExecuteThenCall simplemente crear un subproceso de trabajo, establecer FreeOnTerminate true para simplificar la gestión de memoria y ejecutar las funciones previstas en ejecutar y OnTerminate procedimientos del subproceso de trabajo.

Espero que ayude.

EDITAR (como se pide la plena aplicación 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; 
+0

Esto es muy elegante, pero ¿ha publicado la implementación completa en cualquier lugar? No pude encontrar la implementación completa de la clase TMotileThreading en su publicación. – jamiei

+0

Agregué mi implementación a la respuesta. – jpfollenius

+0

Gracias Smasher: no puedo recordar por qué no lo acepté cuando se publicó originalmente, pero ahora se acepta. ;) – jamiei

5

Estás en el camino correcto. Indy es previsto para ser utilizado de esa manera. Utiliza sockets de bloqueo, por lo que la llamada ReadBytes no se devuelve hasta que no se lea 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 de socket tengan sus propios hilos (o fibras). Indy viene con TIdAntifreeze para las personas que desean arrastrar y soltar componentes de socket en sus formularios y módulos de datos y usar los componentes de Indy del hilo de la GUI principal, pero generalmente no es una buena idea si puedes evitarlo.

Dado que su hilo no puede funcionar sin FSocket asignados, le aconsejo que simplemente reciba ese valor en el constructor de la clase. Afirma en el constructor si no está asignado. Además, es un error para crear su hilo no suspendido, entonces ¿por qué incluso dar la opción? (Si el hilo no se crea suspendido, entonces comenzará a ejecutarse, verifique si FSocket está asignado, y falla porque el hilo de creación no ha llegado a asignar ese campo todavía.)

+0

Ah sí, estás en lo cierto acerca de la CreateSuspended. ¡Eso es un error en el pegado, copié un constructor de un hilo predeterminado porque mi original pasa algo más que sentí que complicaría innecesariamente el código! ¡Mis disculpas! – jamiei

Cuestiones relacionadas