Indy 10 IdTCPClient如何使用独立线程读取数据?

5

问题: 我想知道使用单独的线程在Indy 10中使用IdTCPClient接收数据的最典型或最佳实践方法是什么。

背景: 下面的代码是我尝试做的事情的示例,实际数据处理部分已被删除以增加清晰度。线程的想法是接收所有数据(变量大小,带有声明其余消息长度的标题),然后解析它(这就是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;

作为前缀,我使用了另一个StackOverflow问题来处理Indy的服务器组件: "Delphi 2009, Indy 10, TIdTCPServer.OnExecute, how to grab all the bytes in the InputBuffer" 来获得目前为止的基础。感谢任何帮助!
2个回答

8
如果您想避免为每个客户端-服务器数据交换创建线程类所带来的开销,您可以创建一个可移动的线程类,如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过程中执行提供的函数。
希望这可以帮助你。
编辑(根据要求,完整实现类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类的完整实现。 - jamiei
我将我的实现添加到了答案中。 - jpfollenius
谢谢Smasher - 我不记得为什么我最初没有接受这个,但现在已经被接受了。;) - jamiei
没问题,很高兴能帮助到您。 - jpfollenius

6

您走上了正确的道路。Indy旨在像这样使用。它使用阻塞套接字,因此ReadBytes调用不会返回,直到读取您要求的内容。与非阻塞套接字相比,调用可能会提前返回,因此您需要轮询或异步通知来确定请求何时被填充。

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

由于您的线程无法在未分配FSocket的情况下工作,因此我建议您在类的构造函数中简单地接收该值。如果未分配,则在构造函数中进行断言。此外,创建非暂停线程是一个错误,所以为什么要给出这个选项? (如果线程未创建暂停,则它将开始运行,检查FSocket是否已分配,并因为创建线程尚未获得分配该字段而失败。)


啊,是的,你关于CreateSuspended的说法完全正确。那是粘贴时出现的错误,我从默认线程中复制了一个构造函数,因为我的原始代码传递了其他东西,我觉得这样会使代码变得不必要地复杂!非常抱歉! - jamiei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接