delphi - 释放和终止使用 TIdTCPClient 组件的线程的正确方法是什么?

标签 delphi indy

我正在使用 Delphi 10 Seattle 使用 TIdTCPClientTIdTCPServer 组件构建一个简单的客户端/服务器应用程序。

为了读取从服务器应用程序 (TIdTCPServer) 到达的数据,我在客户端应用程序中使用线程。

这是执行方法

procedure TClientReadThread.Execute;
begin
  while not Terminated do
  begin
    try      
      if FClient.Connected() then //FClient is TIdTCPClient
      begin
       if not FClient.IOHandler.InputBufferIsEmpty then
       begin
         AResponse := FClient.IOHandler.ReadLn();
         Synchronize(NotifyReadln);
       end
       else
       FClient.IOHandler.CheckForDataOnSource(10);
      end;

    except
      on E: Exception do
      begin
        // Send the exception message to the logger
        FE:=E;
        Synchronize(LogException);
      end;
    end;
  end;
end;

在正常情况下,一切工作正常,但现在我正在做一些测试,以在服务器或网络出现故障时恢复客户端应用程序上的连接。因此,我关闭服务器应用程序以模拟通信失败时的问题。

当发生这种情况时,客户端应用程序会使用 TIdTCPClient.OnStatus 事件检测哪个服务器已消失。

之后我尝试使用此代码终止读取线程

  if Assigned(FClientReadThr) then
  begin
    FClientReadThr.Terminate;
    FClientReadThr.WaitFor; // This never returns.
    FreeAndNil(FClientReadThr);
  end;

但是 WaitFor 函数永远不会返回。

问题是,我的执行过程有问题,导致线程无法完成?

是否有更好的方法来终止线程?

最佳答案

首先,您不应该以这种方式使用Connected()。只需无条件调用 ReadLn() 并让它在发生错误/断开连接时引发异常:

procedure TClientReadThread.Execute;
begin
  while not Terminated do
  begin
    try      
     AResponse := FClient.IOHandler.ReadLn();
     Synchronize(NotifyReadln);
    except
      // ...
    end;
  end;
end;

如果您想手动轮询套接字以获取数据,它应该看起来更像这样:

procedure TClientReadThread.Execute;
begin
  while not Terminated do
  begin
    try      
     if FClient.IOHandler.InputBufferIsEmpty then
     begin
       FClient.IOHandler.CheckForDataOnSource(10);
       FClient.IOHandler.CheckForDisconnect;
       if FClient.IOHandler.InputBufferIsEmpty then Continue;
     end;
     AResponse := FClient.IOHandler.ReadLn();
     Synchronize(NotifyReadln);
    except
      // ...
    end;
  end;
end;

请勿使用 TIdTCPClient.OnStatus 事件来检测这种情况下的断开连接。如果直接在 OnStatus 事件处理程序中终止线程,则会导致代码死锁。该事件将在线程的上下文中调用,因为线程是读取连接并检测断开连接的线程。所以你的线程最终会等待自己,这就是 WaitFor() 不退出的原因。

我建议采用另一种方法。根本不要终止线程。要恢复连接,请向线程添加另一级循环,让它检测断开连接并自动重新连接:

procedure TClientReadThread.Execute;
var
  I: Integer;
begin
  while not Terminated do
  begin
    try      
      // don't call Connect() in the main thread anymore, do it here instead
      FClient.Connect;
    except
      // Send the exception message to the logger

      // you should wait a few seconds before attempting to reconnect,
      // don't flood the network with connection requests...
      for I := 1 to 5 do
      begin
        if Terminated then Exit;
        Sleep(1000);
      end;

      Continue;
    end;

    try
      try
        while not Terminated do
        begin
          AResponse := FClient.IOHandler.ReadLn();
          Synchronize(NotifyReadln);
        end;
      except
        // Send the exception message to the logger
      end;
    finally
      FClient.Disconnect;
    end;
  end;
end;

当您想要停止使用套接字 I/O 时,您可以正常Terminate()WaitFor() 线程。

关于delphi - 释放和终止使用 TIdTCPClient 组件的线程的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34697809/

相关文章:

delphi - 如何将代码动态注入(inject)到 Delphi 中的事件处理程序中?

使用 Windows 成像组件 (WIC) 的 Delphi 2007

delphi - 从函数返回 OleVariant 会导致对所包含的 IDispatch 对象产生无法释放的额外引用

sockets - 使用 Indy 套接字进行 UDP 广播 : how to select the right Interface?

德尔福印地 http 选项

Delphi - 当用户单击模式对话框外部时如何生成事件?

ios - 带有 Indy 或第三方 Internet 组件的 iOS 上的 Delphi XE2

web-services - 在运行时为 SMS Web 服务交付设置主机和端口时 TIdHTTP 出错

支持UTF-8或Unicode的Delphi SMTP组件

delphi - Delphi编译,没有错误,但是什么也没发生