在表单关闭时停止线程并断开 Indy tcp 客户端

Stopping thread and disconnecting indy tcp client on form close

我确实写了一个应用程序,它使用 Indy 10 TCP/IP 客户端和 TThread。该应用程序在 Form.OnCreate 事件时连接到服务器,并在 Form.OnClose 事件时断开连接。在TThread.

中实现与服务器的连接

当我在以太网电缆断开连接的情况下启动应用程序并尝试关闭应用程序直到连接超时时,我确实遇到了这两个异常:

如果我在连接到客户端时尝试关闭应用程序,那么我只会得到这个异常:

如果我在线程执行睡眠时关闭应用程序,那么我不会得到异常。

我做错了什么,或者这是正常行为?

TThreadclass代码:

type
  connThread = class (TThread)
  protected
    procedure Execute ; override;
  private
    procedure Sinchronizuot(zinute : string; spalva : TColor; tmrNormalReconn : Boolean);
  end;

Form.OnCreate代码:

procedure TForm1.FormCreate(Sender: TObject);
begin
  fellnerConn := connThread.Create(True);
  fellnerConn.FreeOnTerminate := True;
  fellnerConn.Start;
end;

Form.OnClose代码:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if fellnerConn <> nil then
    fellnerConn.Terminate;
  if idCl.Connected then
  begin
    try
      idCl.Disconnect;
      idCl.IOHandler.Free;  
    finally
      if fellnerConn <> nil then
      begin 
        fellnerConn.WaitFor;
        fellnerConn := nil;
      end;
    end;
  end;
end;

线程执行代码:

procedure connThread.Execute;
var
  zinute : string;
  spalva : TColor;
begin
  inherited;
  while not Form1.fellnerConn.Terminated do
  begin
    zinute := 'Jungiamasi prie Moxa serverio ' + Form1.idCl.Host;
    spalva := clYellow;
    Synchronize(procedure
      begin
        Sinchronizuot(zinute, spalva, False);
      end
    );
    try
      Form1.idCl.Connect;
    except
      on E: Exception do
      begin
        zinute := e.Message + ' Nepavyko prisijungti.';
        spalva := clWebRed;
        Synchronize(procedure
          begin
            Sinchronizuot(zinute, spalva, False);
          end);
        Sleep(1000);
      end;
    end;
  end;
end;

套接字错误在意料之中。主线程正在关闭套接字,而工作线程仍在使用它。

但是,您不能将 TThread.WaitFor()FreeOnTerminate=True 一起使用,这就是您不断收到 "the handle is invalid" 错误的原因。正在销毁线程对象,关闭其句柄,而 WaitFor 仍在使用它。

你不应该这样使用 FreeOnTerminate。它应该只用于启动后忘记类型的线程。一旦需要保留对线程对象的引用,就不应再使用它的 FreeOnTerminate 属性。

无论哪种方式,您都应该使用线程的 OnTerminate 事件,这样您就可以 nil 在线程终止后立即引用该线程。

试试像这样的东西:

type
  connThread = class (TThread)
  protected
    FClient: TIdTCPClient;
    procedure Execute; override;
  private
    procedure Sinchronizuot(zinute : string; spalva : TColor; tmrNormalReconn : Boolean);
  public
    constructor Create(Client: TIdTCPClient); reintroduce;
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  fellnerConn := connThread.Create(IdCl);
  fellnerConn.OnTerminate := ThreadTerminated;
  fellnerConn.Start;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if fellnerConn <> nil then
    fellnerConn.Terminate;
  try
    idCl.Disconnect;
  finally
    if fellnerConn <> nil then
    begin 
      fellnerConn.OnTerminate := nil;
      fellnerConn.WaitFor;
      FreeAndNil(fellnerConn);
    end;
  end;
end;

procedure TForm1.ThreadTerminated(Sender: TObject);
begin
  fellnerConn := nil; 
  TThread.ForceQueue(nil, Sender.Free);
end;

constructor connThread.Create(Client: TIdTCPClient);
begin
  inherited Create(True);
  FClient := Client;
end;

procedure connThread.Execute;
var
  zinute : string;
  spalva : TColor;
begin
  while not Terminated do
  begin
    zinute := 'Jungiamasi prie Moxa serverio ' + FClient.Host;
    spalva := clYellow;
    Synchronize(procedure
      begin
        Sinchronizuot(zinute, spalva, False);
      end
    );
    try
      FClient.Connect;
    except
      on E: Exception do
      begin
        zinute := e.Message + ' Nepavyko prisijungti.';
        spalva := clWebRed;
        Synchronize(procedure
          begin
            Sinchronizuot(zinute, spalva, False);
          end
        );
        if Terminated then Exit;
        Sleep(1000);
        Continue;
      end;
    end;
    try
      // use FClient as needed... 
    finally
      FClient.Disconnect;
    end;
  end;
end;