2009-02-16 12 views
5

Pytanie: Co szukam jest najbardziej typowe lub najlepsze praktyki sposobem użycia oddzielnego wątku do odbierania danych przy użyciu IdTCPClient w Indy 10.Indy 10 IdTCPClient Czytanie danych przy użyciu osobnego wątku?

Tło: Poniższy kod jest próbka tego, co próbuję zrobić z rzeczywistymi częściami przetwarzania danych usuniętymi dla jasności. Idea wątku polega na otrzymywaniu wszystkich danych (zmienny rozmiar z nagłówkiem deklarującym resztę długości komunikatu), a następnie analizowanie go (to robi procedura HandleData) i wyzwalanie Event Handler w zależności od polecenia.

Gniazdo TIdIOHandlerSocket jest przekazywane do wątku przez główną aplikację, która również zapisuje dane w gnieździe, gdy jest to wymagane.

TScktReceiveThread = class(TThread) 
    private 
    { Private declarations } 
    procedure HandleData; 
    protected 
    procedure Execute; override; 
    public 
    FSocket: TIdIOHandlerSocket; 
    constructor Create(CreateSuspended: boolean); 
    end; 


procedure TScktReceiveThread.Execute; 
var 
    FixedHeader: TBytes; 
begin 
    Assert(FSocket <> nil, 'You must assign the connected socket to the receiving thread'); 
    SetLength(FixedHeader, 2); 
    while not Terminated do 
    begin 
     if not FSocket.Connected then 
     Suspend 
     else 
     begin 
      FSocket.CheckForDataOnSource(10); 
      if not FSocket.InputBufferIsEmpty then 
      begin 
      FSocket.ReadBytes(FixedHeader, SizeOf(FixedHeader), false); 
      // Removed the rest of the reading and parsing code for clarity 
      Synchronize(HandleData); 
      end; 
     end; 
    end; 
end; 

jako przedrostek, użyłem innego pytanie StackOverflow która zajmuje się komponentów serwerowych Indy: „Delphi 2009, Indy 10, TIdTCPServer.OnExecute, how to grab all the bytes in the InputBuffer”, aby uzyskać podstawę tego, co mam do tej pory.

Dzięki za pomoc!

Odpowiedz

8

Jeśli chcesz uniknąć nałożonego narzutów poprzez tworzenie klas wątków dla każdej wymiany danych klient-serwer, można utworzyć ruchliwych klasę wątków, jak to opisano w

http://delphidicas.blogspot.com/2008/08/anonymous-methods-when-should-they-be.html

miałem ten sam problem kilka dni temu, a ja po prostu napisał mi klasę TMotileThreading który ma funkcje statyczne, które pozwalają mi tworzyć wątki przy użyciu nowej metody anonimowej metody D2009. Wygląda mniej więcej tak:

type 
    TExecuteFunc = reference to procedure; 

    TMotileThreading = class 
    public 
    class procedure Execute (Func : TExecuteFunc); 
    class procedure ExecuteThenCall (Func : TExecuteFunc; ThenFunc : TExecuteFunc); 
    end; 

Druga procedura pozwala mi wykonywać komunikacji klient-serwer, jak w Twoim przypadku i zrobić kilka rzeczy, gdy dane przyjechał. Zaletą anonimowych metod jest to, że można używać zmiennych lokalnych kontekstu wywoływania.Tak więc komunikacja wygląda mniej więcej tak:

var 
    NewData : String; 
begin 
    TMotileThreading.ExecuteThenCall (
    procedure 
    begin 
     NewData := IdTCPClient.IOHandler.Readln; 
    end, 
    procedure 
    begin 
     GUIUpdate (NewData); 
    end); 
end; 

Execute i sposób ExecuteThenCall prostu utworzyć wątku roboczego, ustaw FreeOnTerminate true uprościć zarządzanie pamięcią i wykonywać funkcje przewidziane w realizacji i OnTerminate procedur wątku pracownika.

Nadzieję, że pomaga.

EDIT (zgodnie z żądaniem pełnego wdrożenia klasy TMotileThreading)

type 
    TExecuteFunc = reference to procedure; 

    TMotileThreading = class 
    protected 
    constructor Create; 
    public 
    class procedure Execute (Func : TExecuteFunc); 
    class procedure ExecuteAndCall (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc; 
           SyncTerminateFunc : Boolean = False); 
    end; 

    TMotile = class (TThread) 
    private 
    ExecFunc    : TExecuteFunc; 
    TerminateHandler  : TExecuteFunc; 
    SyncTerminateHandler : Boolean; 
    public 
    constructor Create (Func : TExecuteFunc); overload; 
    constructor Create (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc; 
         SyncTerminateFunc : Boolean); overload; 
    procedure OnTerminateHandler (Sender : TObject); 
    procedure Execute; override; 
    end; 

implementation 

constructor TMotileThreading.Create; 
begin 
    Assert (False, 'Class TMotileThreading shouldn''t be used as an instance'); 
end; 

class procedure TMotileThreading.Execute (Func : TExecuteFunc); 
begin 
    TMotile.Create (Func); 
end; 

class procedure TMotileThreading.ExecuteAndCall (Func : TExecuteFunc; 
               OnTerminateFunc : TExecuteFunc; 
               SyncTerminateFunc : Boolean = False); 
begin 
    TMotile.Create (Func, OnTerminateFunc, SyncTerminateFunc); 
end; 

constructor TMotile.Create (Func : TExecuteFunc); 
begin 
    inherited Create (True); 
    ExecFunc := Func; 
    TerminateHandler := nil; 
    FreeOnTerminate := True; 
    Resume; 
end; 

constructor TMotile.Create (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc; 
          SyncTerminateFunc : Boolean); 
begin 
    inherited Create (True); 
    ExecFunc := Func; 
    TerminateHandler := OnTerminateFunc; 
    SyncTerminateHandler := SyncTerminateFunc; 
    OnTerminate := OnTerminateHandler; 
    FreeOnTerminate := True; 
    Resume; 
end; 

procedure TMotile.Execute; 
begin 
    ExecFunc; 
end; 

procedure TMotile.OnTerminateHandler (Sender : TObject); 
begin 
    if Assigned (TerminateHandler) then 
    if SyncTerminateHandler then 
     Synchronize (procedure 
        begin 
        TerminateHandler; 
        end) 
    else 
     TerminateHandler; 
end; 
+0

To jest pięknie eleganckie, ale czy opublikowałeś pełną implementację w dowolnym miejscu? Nie mogłem znaleźć pełnej implementacji klasy TMotileThreading w twoim poście. – jamiei

+0

Dodałem moją implementację do odpowiedzi. – jpfollenius

+0

Dziękuję Ci Smasher - nie pamiętam, dlaczego nie zaakceptowałem tego, kiedy pierwotnie pisałem, ale teraz jest akceptowany. ;) – jamiei

5

Jesteś na dobrej drodze. Indy to przeznaczony do użycia w ten sposób. Używa blokowania gniazd, więc wywołanie ReadBytes nie powróci, dopóki nie przeczyta, o co prosiłeś. Należy zwrócić uwagę, że w przypadku nieblokujących gniazd, w których może nastąpić wcześniejsza rozmowa telefoniczna, ankieta lub asynchroniczne otrzymanie powiadomienia w celu ustalenia, kiedy żądanie zostało wypełnione.

Indy został zaprojektowany z oczekiwaniem, że obiekty gniazd mają własne nici (lub włókna). Indy pochodzi z TIdAntifreeze dla osób, które chcą przeciągać i upuszczać komponenty gniazda na swoje formularze i moduły danych i używać komponentów Indy z głównego wątku GUI, ale generalnie nie jest to dobry pomysł, jeśli można tego uniknąć.

Ponieważ twój wątek nie może pracować bez przypisanego FSocket, radzę ci po prostu otrzymać tę wartość w konstruktorze klasy. Sprawdź w konstruktorze, czy nie jest przypisany. Ponadto jest to błąd, aby utworzyć wątek nie zawieszony, więc dlaczego nawet dać opcję? (Jeśli wątek nie zostanie zawieszony, to zacznie działać, sprawdź, czy jest przypisane FSocket, i nie powiedzie się, ponieważ tworzenie wątku nie zostało jeszcze przypisane do przypisania tego pola).

+0

Ach tak, jesteś absolutnie poprawne o CreateSuspended. To jest błąd w sklejce, skopiowałem konstruktora z domyślnego wątku, ponieważ mój oryginał przekazuje coś innego, co według mnie niepotrzebnie komplikowałoby kod! Przepraszam! – jamiei