关闭线程时死锁

Mar*_*mke 1 delphi deadlock asynchronous serial-port

我创建了一个打开COM端口并处理重叠读写操作的类.它包含两个独立的线程 - 一个读取,另一个写入数据.他们都调用OnXXX程序(例如OnRead或OnWrite)来通知完成的读或写操作.

以下是线程如何工作的简短示例:

  TOnWrite = procedure (Text: string);

  TWritingThread = class(TThread)
  strict private
    FOnWrite: TOnWrite;
    FWriteQueue: array of string;
    FSerialPort: TAsyncSerialPort;
  protected
    procedure Execute; override;
  public
    procedure Enqueue(Text: string);
    {...}
  end;

  TAsyncSerialPort = class
  private
    FCommPort: THandle;
    FWritingThread: TWritingThread;
    FLock: TCriticalSection;
    {...}
  public
    procedure Open();
    procedure Write(Text: string);
    procedure Close();
    {...}
  end;

var
  AsyncSerialPort: TAsyncSerialPort;

implementation

{$R *.dfm}

procedure OnWrite(Text: string);
begin
  {...}
  if {...} then
    AsyncSerialPort.Write('something');
  {...}
end;

{ TAsyncSerialPort }

procedure TAsyncSerialPort.Close;
begin
  FLock.Enter;
  try
    FWritingThread.Terminate;
    if FWritingThread.Suspended then
      FWritingThread.Resume;
    FWritingThread.WaitFor;
    FreeAndNil(FWritingThread);

    CloseHandle(FCommPort);
    FCommPort := 0;
  finally
    FLock.Leave;
  end;
end;

procedure TAsyncSerialPort.Open;
begin
  FLock.Enter;
  try
    {open comm port}
    {create writing thread}
  finally
    FLock.Leave;
  end;
end;

procedure TAsyncSerialPort.Write(Text: string);
begin
  FLock.Enter;
  try
    {add Text to the FWritingThread's queue}
    FWritingThread.Enqueue(Text);
  finally
    FLock.Leave;
  end;
end;

{ TWritingThread }

procedure TWritingThread.Execute;
begin
  while not Terminated do
  begin
    {GetMessage() - wait for a message informing about a new value in the queue}
    {pop a value from the queue}
    {write the value}
    {call OnWrite method}
  end;
end;
Run Code Online (Sandbox Code Playgroud)

当您查看Close()过程时,您将看到它进入临界区,终止写入线程,然后等待它完成.由于写入线程可以在调用OnWrite方法时将要写入的新值排入队列,因此在调用TAsyncSerialPort类的Write()过程时,它将尝试进入相同的临界区.

在这里,我们陷入僵局.调用Close()方法的线程进入临界区,然后等待写线程被关闭,同时线程等待临界区被释放.

我一直在思考很长一段时间,我没有找到解决这个问题的方法.问题是我想确保在保留Close()方法时没有读/写线程存活,这意味着我不能只设置那些线程的Terminated标志并离开.

我该如何解决这个问题?也许我应该改变我异步处理串口的方法?

提前感谢您的建议.

马里乌什.

---------编辑----------
这样的解决方案怎么样?

procedure TAsyncSerialPort.Close;
var
  lThread: TThread;
begin
  FLock.Enter;
  try
    lThread := FWritingThread;
    if Assigned(lThread) then
    begin
      lThread.Terminate;
      if lThread.Suspended then
        lThread.Resume;
      FWritingThread := nil;
    end;

    if FCommPort <> 0 then
    begin
      CloseHandle(FCommPort);
      FCommPort := 0;
    end;
  finally
    FLock.Leave;
  end;

  if Assigned(lThread) then
  begin
    lThread.WaitFor;
    lThread.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

如果我的想法是正确的,这应该消除死锁问题.不幸的是,我在写入线程关闭之前关闭了通信端口句柄.这意味着当它调用任何将comm端口句柄作为其参数之一的方法(例如Write,Read,WaitCommEvent)时,应该在该线程中引发异常.我可以确定如果我在该线程中捕获该异常,它将不会影响整个应用程序的工作吗?这个问题可能听起来很愚蠢,但我认为一些例外可能导致操作系统关闭导致它的应用程序,对吗?在这种情况下,我是否需要担心?

mgh*_*hie 6

是的,您应该重新考虑您的方法.异步操作完全可用于消除对线程的需要.如果使用线程,则使用同步(阻塞)调用.如果你使用异步操作,那么处理一个线程中的所有东西 - 不一定是主线程,但IMO在不同的线程中进行发送和接收没有意义.

当然有解决同步问题的方法,但我宁愿改变设计.