Stopping thread and disconnecting indy tcp client on form close

1.6k Views Asked by At

I do write an app, that uses Indy 10 TCP/IP Client and TThread. The app connects to the server on Form.OnCreate event and disconnects from it on Form.OnClose event. Connection to the server is realized in TThread.

When I do start the app while ethernet cable is disconnected and try to close app until connection time out, then I do get these two exeptions:

  • Socket.Error #10038Socket operation on non-socket.
  • Thread Error: The handle is invalid(6).

If I try to close app while it is connected to client, then I get only this exeption:

  • Thread Error: The handle is invalid(6).

If I close the app while thread executes sleep, then no exeptions I do get.

What am I doing wrong, or it is normal behavior?

TThread class code:

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

Form.OnCreate code:

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

Form.OnClose code:

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;

Thread execute code:

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

There are 1 best solutions below

4
On BEST ANSWER

The socket error is to be expected. The main thread is closing the socket while the worker thread is still using it.

But, you cannot use TThread.WaitFor() with FreeOnTerminate=True, that is why you keep getting "the handle is invalid" errors. The thread object is being destroyed, closing its handle, while WaitFor is still using it.

You should not be using FreeOnTerminate like this. It should only be used for start-and-forget type of threads. As soon as you need to keep a reference to a thread object, you should not use its FreeOnTerminate property anymore.

Either way, you should be using the thread's OnTerminate event so you can nil your reference to the thread as soon as the thread has terminated.

Try something more like this:

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;