[delphi] Data uitwisselen tussen threads

Pagina: 1
Acties:

  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 11:54
Update verderop: [rml]Reptile209 in "[ delphi] Data uitwisselen tussen threads"[/rml]


Ik ben een progsel aan het maken dat multi-threaded werkt. Nu loop ik echter constant aan te hikken tegen het uitwisselen van informatie tussen threads.

Even een korte (voorbeeld) situatie:
Er is een main-thread met een form. Op dat form staat een memo, een button en een edit.
Er is een thread die een TCP-connection bijhoudt.

Nu zijn er twee zaken die mogelijk moeten kunnen zijn:
* het form wil data verzenden vanuit de edit na het aanklikken van de button
* binnenkomende data moet teruggegeven worden aan het form om in de memo te worden gezet.

Voor deze laatste uitwisseling (thread -> main) ben ik tot nu toe op een paar verschillende oplossingen gekomen, maar ze kriebelen allemaal een beetje. Even in het kort:

1) Je kan data in een object verpakken dat je maakt in de thread. Een pointer naar dat object stuur je als wparam met een PostMessage() naar de main waar hij wordt afgevangen, verwerkt en vrijgegeven. Probleem daarbij is, dat als de main niks met de message doet (nog niet geimplementeerd, niet interessant, etc), je met een memleak zit wat het object betreft. Is op z'n minst slordig.

2) Je kan direct (via synchronize) vanuit de thread op het memo schrijven. Onzin: dat is lelijk en beperkt je enorm in latere aanpassingen.

3) Je kan een event-systeem gebruiken:
(even in het kort en uit mijn hoofd, maar het idee staat er wel)
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
type
  TMyDataRead = procedure(Sender: TObject; const ReadStr: string; 
    const ReadInt: integer) of Object

type
  TMyDataThread(TThread)
    private
      FOnDataRead: TMyDataRead
      procedure DoDataRead;
    ...
    published
      property OnDataRead: TMyDataRead read FOnDataRead 
        write FOnDataWrite;
      ...
    end;

...

procedure TMyDataRead.Execute;
begin
  ...
  while not Terminated do
  begin
    ...
    if NieuweDataBeschikbaar then
      DoDataRead;
    ...
  end;

procedure TMyDataThread.DoDataRead;
begin
  if assigned(FOnDataRead) then
    FOnDataRead(self, 'Dit is een Test!', 16); 
    // Hier zou je dus normaal "echte" data hebben... :P
end;

...

procedure Form1.OnCreate;
begin
  MyDataRead := TMyDataRead.Create(true);
  MyDataRead.OnDataRead := OnDataRead;
  MyDataRead.FreeOnTerminate := true;
  MyDataRead.Resume;
end;

procedure Form1.OnDataRead(Sender: TObject; const ReadStr: string; 
  const ReadInt: integer);
begin
  Memo1.Lines.Add(ReadStr);
end;

Hier zit ik met de twijfel of dit "automagisch" thread-safe is, en of het dus altijd goed gaat.

Wat is nou een goede, nette en betrouwbare methode om dit soort dingen te doen? Waar moet ik op letten?

Ik kom (vooral in combinatie met synchronize) nogal eens op rare fouten en deadlocks uit: roep vanuit een main functie een thread-functie aan, die vervolgens ergens een synchronize gebruikt: einde verhaal...

Google en de search leveren steeds een heleboel "net niet" verhalen op. Alleen vage verwijzingen naar semaphores, gebruik messages (ja, maar die datastructuur dan), gebruik events (implementatie?), etc.

Iemand ergens wat tips, een gestructureerde tutorial met uitgewerkt voorbeeld, of andere nuttige info?

[ Voor 5% gewijzigd door Reptile209 op 11-05-2003 15:38 ]

Zo scherp als een voetbal!


Verwijderd

een TMemo is in principe niet threadsafe, maw kan er niet tegen als er meerdere threads tegelijkertijd in schrijven. als je echter (en dat maak ik op uit je beschrijving) maar 1 DataRead thread hebt, zal dit niet zo'n groot probleem zijn.

wanneer het form daarentegen ook in de memo schrijft (automatisch of userinput) levert dit potentieel problemen op.

de enige manier is dan om de input naar de memo te serializeren, hetzij door een apart object met een ingebouwde queue, hetzij door middel van synchronize() of critical sections.

  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 11:54
Verwijderd schreef op 07 May 2003 @ 12:43:
een TMemo is in principe niet threadsafe, maw kan er niet tegen als er meerdere threads tegelijkertijd in schrijven. als je echter (en dat maak ik op uit je beschrijving) maar 1 DataRead thread hebt, zal dit niet zo'n groot probleem zijn.

wanneer het form daarentegen ook in de memo schrijft (automatisch of userinput) levert dit potentieel problemen op.

de enige manier is dan om de input naar de memo te serializeren, hetzij door een apart object met een ingebouwde queue, hetzij door middel van synchronize() of critical sections.
De TMemo is op zich ook alleen als voorbeeld. Het is de bedoeling dat de thread alleen data doorgeeft, en dat bij de implementatie van het form wordt bepaald hoe en waar het wordt weergegeven. Het kan zijn dat er inderdaad meerdere threads tegelijk actief zijn, al dan niet aan een eigen form gekoppeld.

Zo scherp als een voetbal!


Verwijderd

* thread -> main zou ik mbv locking doen (semaphore/synchronize/criticalsection), mede omdat je dan 1 op n kan (meer threads op 1 form)
* main -> thread met een event en evt. een bufferqueue in de thread, evt. ook met locking indien noodzakelijk

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Een TMemo op een form en gebruiken in een thread betekend al dat er 2 threads aan hetzelfde stuk geheugen zitten en betekend dus niet threadsafe. Of het form nooit in de TMemo schrijft maakt daarbij niet eens uit.

De Synchronize methode die je noemt kan wat breeder getrokken worden door zelf gebruik te maken van Mutexen, Critical Sections, Windows Events, Semaphores enz... Synchronize zelf gebruikt een windows message om de threads gelijk te krijgen.

Je 3e oplossing is IMHO het mooist omdat het de boel goed los koppelt, maar je niet gedwongen wordt om de soms verwarrende windows messages manier te gebruiken. Zoals jij m gemaakt hebt is het nog niet threadsafe omdat je niet weet wat er in de implementatie van het OnDataRead event gebeurt. Deze wordt in de context van je thread aangeroepen en alles wat daar gebeurt moet dus threadsafe zijn. Een memo vullen is daarmee niet meer threadsafe. Je zal je event dus moeten beschermen door bijvoorbeeld een Synchronize te gebruiken of 1 van de eerdere methoden die ik al opgenoemd heb. Je kan er gewoon niet ondertuit komen met threads.

Verder zou je nog andere methoden kunnen gebruiken dan de drie die je zelf al opnoemt. Infeite is elke IPC methode een kandidaat. Maar ze vereisen bijna allemaal een manier van synchronizeren.

[ Voor 13% gewijzigd door LordLarry op 07-05-2003 13:19 ]

We adore chaos because we like to restore order - M.C. Escher


  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 11:54
Dus als ik dan de DoDataRead-aanroep in Execute zou veranderen in synchronize(DoOnDataRead), zou het goed/beter zijn?

BTW: Wat is IPC? :?

[ Voor 10% gewijzigd door Reptile209 op 07-05-2003 14:05 ]

Zo scherp als een voetbal!


  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Ja

IPC=Inter Process Communication

We adore chaos because we like to restore order - M.C. Escher


  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 11:54
Inmiddels met een paar daagjes pielen een heel stuk verder gekomen: data uitwisselen van thread naar form gaat inmiddels uitstekend.

Maar: nu omgekeerd, dus main thread --> thread. Hoe zorg ik er nu voor dat ik wel een actie in de thread kan triggeren (bijvoorbeeld na het aanklikken van een button) op een thread-safe manier?
Zoals ik het nu doe (en dat werkt niet...), heb ik een public procedure die binnen de thread een aantal acties moet doen. Het probleem zit 'm in acties die hun werk weer via een synchronize-procedure terugkoppelen aan mijn form. In korte code:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type
  TMyDataThread(TThread)
    public
      procedure SetStatus(Value: integer);
      ...
    end;

...

procedure TMyThread.SetStatus(Value: integer);
begin
  { doe wat acties om die status door te werken }
  ...
  FStatus := Value;
  DoStatusUpdate;
end;

procedure DoStatusUpdate;
begin
  if assigned(FDoStatusUpdate) then
    synchronize(DoStatusUpdateSync);  // <- Uiteraard gaat het hier mis
end;

procedure DoStatusUpdateSync;
begin
  { Hier wordt de status, na verandering, geupdate naar het form }
  FDoStatusUpdate(FStatus);
end;

{ Form }
procedure Form1.Button1Click(Sender: TObject);
begin
  MyThread.SetStatus(16);
end;


Ik snap dat, op het moment dat synchronize wordt aangeroepen, het form nog wacht op de afhandeling van SetStatus(), maar dat de thread dan gata wachten tot het form klaar is. Deadlock. Het lastige is, dat DoStatusUpdate ook vanuit andere delen van de thread kan worden aangeroepen, zonder dat het form er ook maar iets mee te maken heeft (eigen trigger dus). Synchronize() heeft dus wel een nuttige functie.

Ik kan me nog een situatie voorstellen waarin ik SetStatus vervang door een variabele, waarvan ik de waarde monitor in bijvoorbeeld Execute(), maar dat vind ik er persoonlijk nogal gemaakt uitzien. Dat moet toch netter kunnen?

Hoe moet ik dit wel aanpakken om acties vanuit het form door te werken in een thread zonder dat ik me al te veel zorgen moet maken over het wederzijds vastzetten van beiden?

[ Voor 3% gewijzigd door Reptile209 op 11-05-2003 15:50 ]

Zo scherp als een voetbal!


  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Windows Messages of niet de algemene Synchronize gebruiken, maar zelf wat inelkaar flansen van Mutexes enz.

Maar ik denk dat je je threads iets te ingewikkeld maakt. Daar zijn threads niet echt voor bedoelt. Over het algemeen worden threads gebruikt om een klein autonoom stukje werk te doen. De threads kunnen gestart en gestopt worden en geven vaak een tussentijds resultaat. Maar meer als dat is vragen om moeilijkheden en vaak ook helemaal niet nodig.

/edit
Ow, en vergeet ook niet de TMultiReadExclusiveWriteSynchronizer

[ Voor 8% gewijzigd door LordLarry op 11-05-2003 17:23 ]

We adore chaos because we like to restore order - M.C. Escher

Pagina: 1