문제

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;

Close () 절차를 보면 중요한 섹션에 들어가서 쓰기 스레드를 종료 한 다음 완료되기를 기다립니다. 쓰기 스레드가 OnWrite 메소드를 호출 할 때 작성해야 할 새 값을 흡수 할 수 있기 때문에 TasyncSerialport 클래스의 write () 절차를 호출 할 때 동일한 중요 섹션을 입력하려고합니다.

그리고 여기 우리는 교착 상태가 있습니다. Close () 메소드라고 불리는 스레드는 임계 섹션에 들어간 다음 쓰기 스레드가 닫히기를 기다리는 반면, 그 스레드가 임계 섹션이 해제 될 때까지 대기합니다.

나는 꽤 오랫동안 생각해 왔으며 그 문제에 대한 해결책을 찾지 못했습니다. 문제는 Close () 메소드가 남을 때 읽기/쓰기 스레드가 살아남지 않기를 원합니다. 즉, 해당 스레드의 종료 된 플래그를 설정하고 떠날 수는 없습니다.

문제를 어떻게 해결할 수 있습니까? 일련의 포트를 비동기 적으로 처리하는 접근 방식을 변경해야할까요?

미리 조언에 감사드립니다.

마리우스.

--------- 편집하다 ----------
그런 해결책은 어떻습니까?

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;

내 생각이 정확하다면 교착 상태 문제를 제거해야합니다. 그러나 불행히도 쓰기 스레드가 닫히기 전에 통신 포트 핸들을 닫습니다. 즉, Comm 포트 핸들을 인수 중 하나로 사용하는 메소드를 호출 할 때 (예 : Write, Read, WaitCommevent) 해당 스레드에서 예외가 제기되어야 함을 의미합니다. 해당 스레드에서 해당 예외를 포착하면 전체 응용 프로그램의 작업에 영향을 미치지 않을 수 있습니까? 이 질문은 어리석게 들릴지 모르지만 일부 예외로 인해 OS가 응용 프로그램을 닫을 수 있다고 생각합니다. 이 경우 그것에 대해 걱정해야합니까?

도움이 되었습니까?

해결책

예, 당신은 당신의 접근 방식을 재고해야 할 것입니다. 스레드의 필요성을 제거하기 위해 비동기 조작을 정확하게 사용할 수 있습니다. 스레드를 사용하는 경우 동기식 (차단) 호출을 사용하십시오. 비동기 작업을 사용하는 경우 한 스레드에서 모든 것을 처리합니다. 반드시 기본 스레드는 아니지만 다른 스레드에서 보내고 수신하는 것이 IMO가 의미가 없습니다.

물론 동기화 문제에 대한 방법이 있지만 디자인을 변경하고 싶습니다.

다른 팁

닫기에서 잠금을 꺼낼 수 있습니다. 그것이 대기자에게서 돌아올 때까지, 실 본체는 그것이 끝났다는 것을 알았고, 마지막 루프를 완료하고, 끝났다.

이 작업을 수행하는 것이 행복하다고 느끼지 않으면 Freeandnil 직전에 자물쇠를 설정할 수 있습니다. 이것은 잠금을 적용하기 전에 스레드 종료 메커니즘이 작동하게합니다 (따라서 잠금을 위해 아무것도 경쟁 할 필요가 없습니다).

편집하다:

(1) Comms 핸들을 닫으려면 실행 또는 스레드의 파괴자에서 루프 후에 수행하십시오.

(2) 죄송하지만 편집 된 솔루션은 끔찍한 혼란입니다. 종료 및 대기자는 필요한 모든 것을 완벽하게 안전하게 수행합니다.

주요 문제는 중요한 섹션에 가까운 내용을 배치하는 것 같습니다. TTHREAD.THERMINATION 및 TTHREAD.WAITFOR가 섹션 외부에서 전화를 걸기 위해 거의 확실합니다 (그러나 문서를 확인해야합니다). 그 부분을 임계 섹션 밖으로 끌어 당기면 교착 상태가 해결됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top