2009-07-05 8 views
1

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; 

, 당신은이 임계 영역에 진입 것을 볼 쓰기 스레드를 종료하고 끝내기를 기다립니다. 쓰기 스레드가 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; 

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

답변

6

네, 아마 당신의 접근 방식을 재고해야합니다. 비동기 작업은 스레드가 필요 없도록 정확하게 사용할 수 있습니다. 스레드를 사용하는 경우 동기 (차단) 호출을 사용하십시오. 비동기 작업을 사용하는 경우 하나의 스레드 (모든 스레드는 반드시 주 스레드는 아님)에서 모든 것을 처리하지만 다른 스레드에서 보내고받는 일은 IMO가 이해하지 못합니다.

물론 동기화 문제를 해결하는 방법은 있지만 설계를 변경하는 것이 좋습니다.

+0

나는 당신이 절대적으로 옳다고 생각한다. 그러나 왜이 방법을 선택했는지에 대한 이유가 있습니다. 동기 방식을 선택하면 마스크에 의해 지정된 이벤트가 발생할 때까지 WaitCommEvent() 메서드가 스레드를 차단합니다. 즉 이벤트가 발생하지 않으면 스레드를 닫을 수 없다는 의미입니다. 아니면 직렬 포트 핸들을 닫는 것만으로도 충분할 수 있으며 함수는 예외를 제외하고 리턴 할 것입니다. –

+0

반면에, 나는 장치의 응답을 기다릴 수있는 두 개의 독립적 인 스레드를 생성한다. 모뎀과 통신한다고 가정 해 봅시다. 예를 들어 "AT"명령을 쓰면 모뎀이 "OK"또는 "ERROR"로 응답 할 것으로 예상됩니다. 이벤트 (TEvent)를 재설정하고, 이벤트가 읽기 스레드의 OnRead 메소드에 의해 설정 될 때 리턴하는 WaitForSingleObject() 메소드를 호출하십시오. 거기에 단 하나의 읽기/쓰기 스레드가 있다면, 나는 같은 일을 메인 응용 프로그램의 스레드를 차단해야 할 것이다. (. 맞아요? –

+0

통신 할 단 하나의 직렬 포트가 있다면 아마도 비동기 I/O를 사용할 것입니다 하나 이상의 작업 스레드에서 비동기 I/O를 사용할 것입니다. 또한 백그라운드 스레드에서 동기식 또는 비동기식 I/O를 사용할지 여부는 데이터를 수신 할 때 미리 알고 있는지 여부에 달려 있습니다 WaitCommEvent()를 사용하면 왜 항상 WaitCommEvent()를 사용할 수 있습니까? 현명한 타임 아웃 값을 설정 한 후 직접 읽으십시오. 언제든지 상대방이 WaitCommEvent()를 보내면 더 많은 의미를 갖을 수 있습니다 .. – mghie

2

주요 문제는 중요한 섹션에 Close의 전체 내용을 배치 한 것 같습니다. 나는 TThread.Terminate와 TThread.WaitFor가 섹션 외부에서 호출하는 것이 안전하다는 것을 거의 확신한다 (그러나 당신은 문서를 체크해야 할 것이다). 임계 구역 밖에서 그 부분을 당김으로써 교착 상태를 해결할 수 있습니다.

+0

그래,하지만 Open() 메서드를 사용하면 FWritingThread 변수에 nil을 할당해야한다는 것이므로 필기 스레드를 다시 작성해야하는지 여부를 알아야합니다. 그러나 좋은 생각입니다. 내 편집 된 질문을보십시오. 감사. –

+0

수정 사항에 대한 귀하의 제안은 무엇보다도 나쁜 것입니다. 죄송합니다. 잠금 장치가 필요한 것은주의 깊게 고려해야하며, COM 포트 일뿐입니다. 스레드가 아닙니다. 첫 번째 버전에서 자물쇠를 꺼내면 작동 할 것입니다. –

+0

네, 이해합니다. 그러나 이것은 전체 코드의 일부분 일 뿐이며 FWritingThread 변수는 다른 스레드에서 동시에 할당 할 수 있습니다. 따라서 Critical 섹션에 Close() 메서드 코드를 추가하는 이유입니다. 그래도, 당신 말이 맞아요. 코드가 어색 해요. :) 그러나이 주제를 떠나 봅시다. 전체 직렬 포트 처리 방식의 변경을 고려하고 있습니다. 그럼에도 불구하고, 당신의 지원에 감사드립니다. –

4

닫기에서 잠금을 해제 할 수 있습니다.WaitFor에서 리턴 할 때까지는 스레드 본문이 종료되었음을 감지하고 마지막 루프를 완료 한 후 종료합니다.

당신이 행복하게 느끼지 않는다면 FreeAndNil 바로 전에 잠금 설정을 이동할 수 있습니다. 당신은 또한 COMMS을 닫으려면

이 이

(1) 처리 :이 명시 적으로

편집 (가 잠금 무엇과 경쟁 할 필요가 없습니다 그래서) 당신이 잠금을 적용하기 전에 스레드 종료 메커니즘이 작동 할 수 있습니다 Execute 또는 스레드의 소멸자에서 루프를 수행하십시오.

(2) 죄송하지만 편집 된 솔루션은 끔찍한 혼란입니다. Terminate 및 Waitfor는 필요한 모든 것을 완벽하게 안전하게 처리합니다.

관련 문제