2013-08-19 17 views
0

우리는 INDY를 사용하는 Delphi 클라이언트 서버 응용 프로그램을 가지고 있습니다. 클라이언트는 다중 스레드 된 서버에 대한 단일 tIdTCPClient 연결을가집니다. 클라이언트는 "이론적으로"단일 스레드입니다. 그러나 실제로는 클라이언트에 여러 스레드가 있고 이것은 내 문제가있는 곳입니다. 예를 들어 매분마다 서버에서 데이터를 가져 오는 타이머를 생각해보십시오. 그리고 사용자가이 타이머 이벤트와 동시에 명령을 실행할 때 어떤 일이 일어나는지 고려하십시오. 사실, 내 문제는 (귀찮게) 보고서의 모든 페이지를로드하는 데 시간이 걸리는 "보고서 작성기"보고 도구로 인해 발생합니다. 이 보고서는 한 번에 여러 레코드를 전송하는 캐싱 메커니즘이있는 "특수"데이터 세트에서 실행됩니다 (따라서 모든 데이터를 얻기 위해 서버를 여러 번 호출합니다). 동시에 사용자가 다른 작업을 수행하면 교차 데이터가 표시되는 것 같습니다. 사용자가 보고서 용 데이터를 되 찾는 것 같습니다.타이머 이벤트 및 기타 멀티 스레드 클라이언트 측 이벤트가있는 Delphi tIdTCPClient

그러나이 버그는 매우 드뭅니다. 그러나 세계에서 가장 느린 인터넷을 가진 특정 고객에게는이 버그가별로 드물지 않습니다. (행운을 빌어 이제 테스트 환경이 있습니다.)

그래서 클라이언트에 나는 코드를 가지고 같은 비트 ... 나는 그것을 디버깅 할 때이 절차의 중간에 두 개의 스레드가있을 때

procedure DoCommand(MyIdTCPClient:tIdTCPClient; var DATA:tMemoryStream); 
var 
    Buffer: TBytes; 
    DataSize: Integer; 
    CommsVerTest: String; 
begin 
    //Write Data 
    MyIdTCPClient.IOHandler.Write(DATA.Size); 
    MyIdTCPClient.IOHandler.Write(RawToBytes(Data.Memory^,DataSize)); 

    //Read back 6 bytes CommsVerTest should always be the same (ie ABC123) 
    SetLength(Buffer,0); //Clear out buffer 
    MyIdTCPClient.IOHandler.ReadBytes(Buffer,6); 
    CommsVerTest:=BytesToString(Buffer); 
    if CommsVerTest<>'ABC123' then 
    raise exception.create('Invalid Comms');  //It bugs out here in rare cases 

    //Get Result Data Back from Server 
    DataSize:=MyIdTCPClient.IOHandler.ReadLongInt; 
    Data.SetSize(DataSize);       //Report thread is stuck here 
    MyIdTCPClient.IOHandler.ReadBytes(Buffer,DataSize); 
end; 

지금, 내가 버그를 확인할 수 있습니다. 예외시 주 스레드가 중지됩니다. 그리고 보고서 스레드는 동일한 절차의 다른 어딘가에 붙어 있습니다.

그래서, 스레드 안전 위의 절차를 만들어야 할 것처럼 보입니다. 나는 사용자가 무언가를 원할 경우보고 스레드가 끝날 때까지 기다려야한다는 것을 의미합니다.

Arrrgh, 제 클라이언트 응용 프로그램이 서버로 데이터를 보내는 단일 스레드라고 생각했습니다!

보고서 작성기 내부의 스레드에 액세스 할 수 없어 TThread를 사용하면 작동하지 않는다고 생각합니다. 나는 tCriticalSection이 필요하다고 생각합니다.

위의 절차를 한 번에 하나의 스레드에서만 실행할 수 있도록 응용 프로그램을 만들어야한다고 생각합니다. 다른 스레드는 기다려야합니다.

누군가가 구문을 사용하십시오.

답변

2

TIdIOHandler는 수신/TStream 데이터를 전송하는 Write()Read...() 과부하가 있습니다

procedure Write(AStream: TStream; ASize: TIdStreamSize = 0; AWriteByteCount: Boolean = False); overload; virtual; 

procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1; AReadUntilDisconnect: Boolean = False); virtual; 

당신은 그것을 보내기 전에 중간 TIdBytesTMemoryStream 내용을 복사, 또는 수신 할 필요가 없습니다 다시 TIdBytes으로 복사 한 다음 TStream으로 다시 복사합니다. 사실, 직접 모든 TIdBytes을 사용할 필요는 보여 코드에 아무것도 없다 : 말했다와

procedure DoCommand(MyIdTCPClient: TIdTCPClient; var DATA: TMemoryStream); 
var 
    CommsVerTest: String; 
begin 
    //Write Data 
    MyIdTCPClient.IOHandler.Write(DATA, 0, True); 

    //Read back 6 bytes CommsVerTest should always be the same (ie ABC123) 
    CommsVerTest := MyIdTCPClient.IOHandler.ReadString(6); 
    if CommsVerTest <> 'ABC123' then 
    raise exception.create('Invalid Comms'); 

    //Get Result Data Back from Server 
    DATA.Clear; 
    MyIdTCPClient.IOHandler.ReadStream(DATA, -1, False); 
end; 

당신은 같은 시간에 같은 소켓에 쓰기, 또는 여러 다중 스레드가있는 경우 동일한 소켓에서 동시에 읽는 스레드는 서로의 데이터를 손상시킵니다 (또는 악화됩니다). 최소 임계 영역과 같이 소켓에 대한 액세스를 동기화해야합니다. TIdTCPClient의 다중 스레드 사용으로 인해 전체 클라이언트 설계를 다시 생각해 내야합니다.

적어도 기존 논리를 사용하여 명령을 보내고 응답을 읽어야 할 때 타이머를 중지하고 명령을 보내기 전에 보류중인 데이터가 교환 될 때까지 기다렸다가 다른 것을 허용하지 마십시오 응답이 돌아올 때까지 소켓에 액세스합니다. 겹치는 것을 피하기 위해 모든 것을 동기화하지 않고 한 번에 너무 많이 수행하려고합니다.

장기적으로 단일 전용 스레드에서 모든 읽기를 수행 한 다음 수신 된 데이터를 다른 스레드로 전달하여 필요에 따라 처리하는 것이 훨씬 안전합니다. 그러나 이는 전송 논리를 변경하여 일치시키는 것을 의미합니다. 당신은 할 수 중 하나 : 당신의 프로토콜 병렬 비행에서 여러 명령을 가질 수있는 경우

  1. 는, 당신은 언제든지 모든 스레드에서 명령을 보낼 수 있습니다 (다만 중복을 피하기 위해 중요 섹션을 사용하십시오) , 응답을 즉시 기다리지는 마라. 각 보내는 스레드가 계속 이동하고 다른 작업을 수행하게하고 예상되는 응답이 실제로 도착할 때 읽기 스레드가 적절한 보내는 스레드에게 비동기 적으로 알리도록합니다.

  2. 프로토콜이 병렬 명령을 허용하지 않지만 여전히 각 송신 스레드가 해당 응답을 기다릴 필요가있는 경우 소켓 스레드는 다른 스레드가 필요할 때 명령을 보낼 수있는 스레드 안전 큐를 제공하십시오. 소켓 쓰레드는 큐를 통해 주기적으로 각 커맨드를 보내고 응답을 한 번에 하나씩 수신 할 수 있습니다. 대기열에 명령을 넣는 각 스레드는 응답이 도착할 때 신호를받을 수있는 TEvent을 포함 할 수 있습니다. 즉, 기다리는 동안 효율적인 절전 모드로 전환되지만 스레드 당 대기 논리를 보존 할 수 있습니다.

0

감사합니다.

TCriticalSection이 문제를 해결했습니다. 제 3 자 보고서 작성 도구와 같은 것을 제어 할 권한이 없습니다. 그리고 자신의 스레드에서 보고서를 실행해도 큰 차이는 없습니다. 여전히 동일한 연결을 공유해야합니다 (필자는 병렬 연결이 필요하거나 필요하지 않습니다). 어쨌든 프로그램의 대부분은 주 스레드에서 실행되며 두 스레드가 동시에 서버와 통신해야하는 경우는 거의 없습니다.

그래서 TCriticalSection은 완벽했기 때문에이 절차가 동시에 두 번 실행되지 않았습니다. 즉, 첫 번째 스레드가 완료 될 때까지 하나의 스레드가 대기해야했습니다. 그리고 행복하게 - 그것은 훌륭하게 작동했습니다.

는 기본적으로 코드는 이제 다음과 같습니다

procedure DoCommand(
    CS:tCriticalSection; 
    MyIdTCPClient:tIdTCPClient; 
    var DATA:tMemoryStream); 
var 
    Buffer: TBytes; 
    DataSize: Integer; 
    CommsVerTest: String; 
begin 
    CS.Enter;  //enter Critical Section 
    try 
    //Write Data 
    MyIdTCPClient.IOHandler.Write(DATA.Size); 
    MyIdTCPClient.IOHandler.Write(RawToBytes(Data.Memory^,DataSize)); 

    //Read back 6 bytes CommsVerTest should always be the same (ie ABC123) 
    SetLength(Buffer,0); //Clear out buffer 
    MyIdTCPClient.IOHandler.ReadBytes(Buffer,6); 
    CommsVerTest:=BytesToString(Buffer); 
    if CommsVerTest<>'ABC123' then 
     raise exception.create('Invalid Comms');  

    //Get Result Data Back from Server 
    DataSize:=MyIdTCPClient.IOHandler.ReadLongInt; 
    Data.SetSize(DataSize);       
    MyIdTCPClient.IOHandler.ReadBytes(Buffer,DataSize); 
    finally 
    cs.Leave; 
    end; 
end;