1

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

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

  • Socket.Error #10038 非套接字上的套接字操作。
  • 线程错误:句柄无效(6)。

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

  • 线程错误:句柄无效(6)。

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

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

TThread班级代码:

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;
4

1 回答 1

2

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

但是,您不能使用TThread.WaitFor()with FreeOnTerminate=True,这就是为什么您不断收到“句柄无效”错误的原因。线程对象正在被销毁,关闭它的句柄,同时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;
于 2018-11-07T16:44:01.570 回答