Indy 10 IdTCPClient Reading Data using a separate thread?

后端 未结 2 1845
心在旅途
心在旅途 2020-12-29 15:57

Question: What I\'m looking for is the most typical or best practice way to use a separate thread to receive data using an IdTCPClient in Indy 10.

相关标签:
2条回答
  • 2020-12-29 16:46

    If you want to avoid the overhead imposed by creating thread classes for each and every client-server data exchange, you could create a motile threading class as described in

    http://delphidicas.blogspot.com/2008/08/anonymous-methods-when-should-they-be.html

    I had the same problem a few days ago and I just wrote me a class TMotileThreading which has static functions that let me create threads using the new anonymous method feature of D2009. Looks something like this:

    type
      TExecuteFunc = reference to procedure;
    
      TMotileThreading = class
      public
        class procedure Execute (Func : TExecuteFunc);
        class procedure ExecuteThenCall (Func : TExecuteFunc; ThenFunc : TExecuteFunc);
      end;
    

    The second procedure allows me to perform a client-server communication like in your case and do some stuff whenever the data has arrived. The nice thing about anonymous methods is that you can use the local variables of the calling context. So a communication looks something like this:

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

    The Execute and ExecuteThenCall method simply create a worker thread, set FreeOnTerminate to true to simplify memory management and execute the provided functions in the worker thread's Execute and OnTerminate procedures.

    Hope that helps.

    EDIT (as requested the full implementation of class 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 讨论(0)
  • 2020-12-29 16:49

    You're on the right track. Indy is intended to be used like that. It uses blocking sockets, so the ReadBytes call doesn't return until it's read what you've asked for. Contrast that with non-blocking sockets, where a call may return early, so you either poll or get notified asynchronously to determine when a request has been filled.

    Indy is designed with the expectation that the socket objects have their own threads (or fibers). Indy comes with TIdAntifreeze for the folks who want to drag and drop socket components onto their forms and data modules and use the Indy components from the main GUI thread, but that's not generally a good idea if you can avoid it.

    Since your thread cannot work without FSocket being assigned, I advise you to simply receive that value in the class's constructor. Assert in the constructor if it's not assigned. Furthermore, it is an error to create your thread non-suspended, so why even give the option? (If the thread is not created suspended, then it will start running, check whether FSocket is assigned, and fail because the creating thread hasn't gotten to assign that field yet.)

    0 讨论(0)
提交回复
热议问题