释放和终止使用 TIdTCPClient 组件的线程的正确方法是什么?
What is the proper way to free and terminate a thread which uses the TIdTCPClient component?
我正在使用 Delphi 10 Seattle 使用 TIdTCPClient
和 TIdTCPServer
组件构建一个简单的 Client/Server 应用程序。
为了读取来自服务器应用程序 (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
函数从来没有 returns。
所以问题是,我的执行过程有问题导致线程无法完成?
是否存在更好的终止线程的方法?
首先,您不应该以这种方式使用 Connected()
。只需无条件地调用 ReadLn()
并让它在 error/disconnect 发生时引发异常:
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;
当您想停止使用套接字时,您可以 Terminate()
和 WaitFor()
线程正常 I/O。
我正在使用 Delphi 10 Seattle 使用 TIdTCPClient
和 TIdTCPServer
组件构建一个简单的 Client/Server 应用程序。
为了读取来自服务器应用程序 (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
函数从来没有 returns。
所以问题是,我的执行过程有问题导致线程无法完成?
是否存在更好的终止线程的方法?
首先,您不应该以这种方式使用 Connected()
。只需无条件地调用 ReadLn()
并让它在 error/disconnect 发生时引发异常:
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;
当您想停止使用套接字时,您可以 Terminate()
和 WaitFor()
线程正常 I/O。