【问题标题】:Delphi - Multithreading: Why I can't start thread again after thread.terminate()?Delphi - 多线程:为什么我不能在 thread.terminate() 之后再次启动线程?
【发布时间】:2018-04-23 20:01:18
【问题描述】:

我编写了一个multithread 应用程序来发送和接收 TCP 包。我遇到的问题是,当我调用下面代码的两次事件confirmBoxRecognized(peerIP: string) 时。我收到以下异常:

无法在正在运行或挂起的线程上调用 Start

如果我签入线程对象,我有 terminated == truesuspended == false。为什么我编码错误?

按照代码:

TThreadReadTCP = class(TThread)

  private

    context: TfrmBoxTest;
    looping: Boolean;

    procedure readTCP;
  protected
    procedure DoTerminate; override;
    procedure Execute; override;

  public
    peerIP: String;
    responseObject: TProtocolObject;

    constructor Create(CreateSuspended: Boolean; ctx: TFrmBoxTest); overload;


  end;


{ TThreadReadTCP }

constructor TThreadReadTCP.Create(CreateSuspended: Boolean; ctx: TFrmBoxTest);
begin
  inherited Create(CreateSuspended);
  Self.context := ctx;
  FreeOnTerminate := True;
end;


procedure TThreadReadTCP.DoTerminate;
begin

  looping := false;
  inherited DoTerminate();

end;

procedure TThreadReadTCP.Execute;
begin
  inherited;
  looping := true;
  readTCP;
end;

procedure TThreadReadTCP.readTCP;
var
  buffer: TBytes;
begin

  while looping do
  begin

    if context.tcpClientBox.Connected then
    begin

      try

        buffer := TEncoding.ASCII.GetBytes(context.tcpClientBox.Socket.ReadLn());

        //do something else

      except on E:Exception  do
        ShowMessage('Error receiving TCP buffer with message: ' + e.Message);
      end;

    end;

  end;

end;



procedure TfrmBoxTest.confirmBoxRecognized(peerIP: string);
begin
  if (connectBoxTCP(peerIP)) then
  begin
    if Assigned(threadReadTCP) then
    begin
      threadReadTCP.Terminate();
      threadReadTCP.Start(); // I get the exception here when I run this code twice...
    end;
    showBoxRecognized();
  end;
  sendBoxRecognized();
end;

我可以获得运行线程状态吗?或者任何人都可以解释我如何改进这段代码来解决这个问题?

非常感谢!

【问题讨论】:

  • 所以线程是terminated=true但是还没写完?
  • 就这样。
  • terminated 只是一个布尔值,如果您希望线程终止,您可以设置。您可以在 while 循环中检查 terminated 来结束它,而不是检查 looping
  • AFAIK,我不记得有可能重新启动终止的线程。甚至从未尝试过。终止意味着终止。如果要重新启动它,请不要终止它。将它保持在一个循环中,该循环检查一些标志是否应该继续。如果没有,那么就继续循环和检查。要么,要么只是开始一个新的线程副本。事实上,如果您使用FreeOnTerminate,那么从您调用Start 的那一刻起,您确实 想要保留对该线程的引用。从那一刻起,它必须被忽略。声明一个局部变量,而不是在表单中。
  • 另外,好的线程设计是将其与所有 UI 控件隔离开来。这最好通过在它自己的单元中编写线程来完成,它甚至不使用任何 VCL 单元。它应该是独立的,并且不知道它是如何被使用的。 UI 更新应该以某种方式同步。我个人更喜欢触发同步事件。在任何情况下,线程都不能直接访问主 UI 线程中的任何 UI 控件,因为它们不是线程安全的。

标签: multithreading delphi delphi-xe3


【解决方案1】:

您会收到异常,因为您只能在 TThread 对象上调用一次 Start()。线程一旦启动,您将无法重新启动它。一旦收到终止的信号,您所能做的就是等待它完成终止,然后销毁该对象。

如果你想让另一个线程开始运行,你必须创建一个新的TThread对象,例如:

type
  TThreadReadTCP = class(TThread)
  private
    context: TfrmBoxTest;
  protected
    procedure Execute; override;
    procedure TerminatedSet; override;
  public
    peerIP: String;
    responseObject: TProtocolObject;
    constructor Create(ctx: TFrmBoxTest); reintroduce;
  end;

constructor TThreadReadTCP.Create(ctx: TFrmBoxTest);
begin
  inherited Create(False);
  Self.context := ctx;

  // NEVER use FreeOnTerminate=True with a thread object that you keep a reference to!
  // FreeOnTerminate := True;
end;

procedure TThreadReadTCP.Execute;
var
  buffer: TBytes;
begin
  while not Terminated do
  begin
    try
      buffer := TEncoding.ASCII.GetBytes(context.tcpClientBox.Socket.ReadLn());
      // do something else
    except
      on E: Exception do
      begin
        // do something
        raise;
      end;
    end;
  end;
end;

procedure TThreadReadTCP.TerminatedSet;
begin
  try
    context.tcpClientBox.Disconnect(False);
  except
  end;
end;

...

procedure TfrmBoxTest.confirmBoxRecognized(peerIP: string);
begin
  if Assigned(threadReadTCP) then
  begin
    threadReadTCP.Terminate();
    threadReadTCP.WaitFor();
    FreeAndNil(threadReadTCP);
  end;
  if connectBoxTCP(peerIP) then
  begin
    threadReadTCP := TThreadReadTCP.Create(Self);
    showBoxRecognized();
  end;
  sendBoxRecognized();
end;

【讨论】:

  • 感谢@Remy Labeau 的精彩回答!我有最后一个问题。调用 TerminatedSet Execute 后引发异常:“未连接”。这是正常行为吗?
  • @pedro.olimpio 是的,因为Terminate() 调用TerminatedSet() 以在Execute() 运行时断开套接字。这是解锁ReadLn() 的唯一方法,除非您在调用ReadLn() 时使用读取超时
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-24
  • 2021-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-29
  • 1970-01-01
相关资源
最近更新 更多