我正在使用Delphi 10 Seattle来使用TIdTCPClient
和TIdTCPServer
组件构建一个简单的客户端/服务器应用程序.
为了读取从服务器应用程序(TIdTCPServer)到达的数据,我在客户端应用程序中使用了一个线程.
这是Execute方法
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;
在正常情况下一切正常,但现在我正在做一些测试,以恢复客户端应用程序上的连接,以防服务器或网络出现故障.所以我关闭服务器App以模拟通信失败时的问题.
发生这种情况时,客户端应用程序会使用该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()
线程.