【问题标题】:Indy 10 IdTCPClient Reading Data using a separate thread?Indy 10 IdTCPClient 使用单独的线程读取数据?
【发布时间】:2009-02-16 18:41:17
【问题描述】:

问题:我正在寻找的是最典型或最佳实践在 Indy 10 中使用 IdTCPClient 使用单独线程接收数据的方法。 p>

背景: 下面的代码是我尝试删除的实际数据处理部分的示例,为清楚起见。线程的想法是接收所有数据(带有声明其余消息长度的标头的可变大小)然后解析它(这就是 HandleData 过程所做的)并根据命令触发事件处理程序。

TIdIOHandlerSocket 由主应用程序传递给线程,主应用程序也会在需要时将数据写入套接字。

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;

作为前缀,我使用了另一个处理 Indy 的服务器组件的 StackOverflow 问题:“Delphi 2009, Indy 10, TIdTCPServer.OnExecute, how to grab all the bytes in the InputBuffer”来了解我目前所拥有的基础。

感谢您的帮助!

【问题讨论】:

    标签: multithreading delphi sockets indy


    【解决方案1】:

    如果您想避免为每个客户端-服务器数据交换创建线程类所带来的开销,您可以创建一个移动线程类,如

    中所述

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

    几天前我遇到了同样的问题,我刚刚给我写了一个 TMotileThreading 类,它具有静态函数,让我可以使用 D2009 的新匿名方法特性创建线程。看起来像这样:

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

    第二个过程允许我像您的情况一样执行客户端-服务器通信,并在数据到达时执行一些操作。匿名方法的好处是您可以使用调用上下文的局部变量。所以通信看起来像这样:

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

    Execute 和 ExecuteThenCall 方法只是创建一个工作线程,将 FreeOnTerminate 设置为 true 以简化内存管理并在工作线程的 Execute 和 OnTerminate 过程中执行提供的函数。

    希望对您有所帮助。

    EDIT(根据要求完整实现 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;
    

    【讨论】:

    • 这非常优雅,但是您是否在任何地方发布了完整的实现?我无法在您的帖子中找到 TMotileThreading 类的完整实现。
    • 我在答案中添加了我的实现。
    • 谢谢你 Smasher - 我不记得为什么我在最初发布时不接受这个,但现在它被接受了。 ;)
    • 没问题,很高兴能帮上忙
    【解决方案2】:

    你在正确的轨道上。 Indy 打算这样使用。它使用阻塞套接字,因此ReadBytes 调用在读取您所要求的内容之前不会返回。与非阻塞套接字相比,调用可能会提前返回,因此您可以轮询或异步获取通知以确定何时完成请求。

    Indy 的设计期望套接字对象有自己的线程(或纤维)。 Indy 附带了TIdAntifreeze,供那些想要将套接字组件拖放到他们的表单和数据模块上并从主 GUI 线程使用 Indy 组件的人使用,但如果可以避免,这通常不是一个好主意。

    由于没有分配FSocket,您的线程无法工作,我建议您在类的构造函数中简单地接收该值。如果未分配,则在构造函数中断言。此外,创建非暂停线程是一个错误,那么为什么还要给出这个选项呢? (如果创建的线程没有挂起,则开始运行,检查是否分配了FSocket,失败是因为创建线程还没有分配到该字段。)

    【讨论】:

    • 啊,是的,您对 CreateSuspended 是绝对正确的。这是粘贴中的一个错误,我从默认线程复制了一个构造函数,因为我的原始传递了一些我认为不必要地使代码复杂化的东西!抱歉!
    猜你喜欢
    • 1970-01-01
    • 2013-12-02
    • 2013-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多