Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Mehrere Container für mehrere Threads? (https://www.delphipraxis.net/109347-mehrere-container-fuer-mehrere-threads.html)

TheMiller 28. Feb 2008 14:28


Mehrere Container für mehrere Threads?
 
Hallo,

habe mich vor ein paar Tagen mit einigen aus dem Forum über Container und Threads unterhalten. Ich habe eine Containerklasse und ein Thread-Objekt erstellt. In der Containerklasse befindet sich ein Array, indem Daten vom Thread abgelegt und aufbereitet werden.

Das klappt alles wunderbar. Nur jetzt bekomme ich langsam das Problem, dass ich manchmal mehrere Threads gleichzeitig laufen habe, aber immer nur den gleichen Container. Ab un zu kommen Fehlermeldungen. Ich glaube, dass es Probleme gibt, wenn mehrere Threads auf das gleiche Array zugreifen und die Daten schnell nacheinander verändern. Denn das Hauptprogramm soll sich die Daten aus dem Array nehmen und anzeigen. Doch während es die Daten von Thread1 anzeigen möchte, hat Thread2 schon die Daten verändert und eine andere Struktur gespeichert. Nun kommt es zu AVs beim Anzeigen.

Nun ist die Frage, ob ich für jeden Thread einen eigenen Container erstellen soll und wann ich ihn dann freigeben soll? Nachteil wäre, dass ich wieder viele globale Container-Variablen habe, denn der Auftrag einen Thread zu erstellen und das Anzeigen der Daten sind in verschiedenen Methoden.

Ich hoffe, ihr versteht, was ich meine. Hier mal der Array-Code aus der Container-Klasse:

Delphi-Quellcode:
type
  TExpData = Array of String;
type
  TContainer=class(TSimpleRWSync)
  private
    FExpData: TExpData;

    function getExpData(i: Integer): String;
    function getExpDataCount: Integer;
  public
    procedure AddExpData(const Value: String);
    procedure ClearExpData;
    property ExpData[i: Integer]: String read getExpData;
    property ExpDataCount: Integer read getExpDataCount;
  end;

function TContainer.getExpData(i: Integer): String;
begin
  BeginRead;
  try
    result:=FExpData[i];
  finally
    EndRead;
  end;
end;

procedure TContainer.AddExpData(const Value: String);
begin
  BeginWrite;
  try
    SetLength(FExpData, Succ(Length(FExpData)));
    FExpData[High(FExpData)]:=Value;
  finally
    EndWrite;
  end;
end;

procedure TContainer.ClearExpData;
begin
  BeginWrite;
  try
    SetLength(FExpData, 0);
  finally
    EndWrite;
  end;
end;

function TContainer.getExpDataCount: Integer;
begin
  result:=High(FExpData);
end;
Danke im Voraus

sirius 28. Feb 2008 14:43

Re: Mehrere Container für mehrere Threads?
 
GetExpDataCount solltest du auch in beginread und endread einbauen.

Wenn du nun gleichzeitig aus mehreren Threads Ergebnisse haben möchtest, dann musst du auch mehrere Container erstellen, oder in dem einen einen Container eine Logik entwickeln, dass du z.B. je ThreadID ein eigenes TExpData anlegst.

Aber global hast du doch hoffentlich die Container nicht. Du musst den Container im MainThread anlegen. Der Container selbst ist ein Feld vom Mainthread. Und dem Thread übergibst du nur (bspws.: im Constructor) den Container. also nicht als Kopie sondern nur dein Zeiger so wie er ist (einfache Zuweisung). Da kannst du auch verschiedene Container je Thread nehmen. Aber bitte nicht global.

Tyrael Y. 28. Feb 2008 14:43

Re: Mehrere Container für mehrere Threads?
 
Dein Problem kannst du sehr einfach mit CriticalSections lösen.

Inplementiere eine TCriiticalSection als Feldvariable in deine Thread-Klasse.
Immer wenn auf gemeinsam genutzte Dateien, Objekte usw. zugegriffen wird, machts du das innerhalb einer CriticalSection.

Delphi-Quellcode:

...
FCriticalSection.Enter;
try
  //Zugriff auf gemeinsam genutzte Objekte, Dateien usw.
finally
  FCriticalSection.Leave;
end;
...

sirius 28. Feb 2008 14:45

Re: Mehrere Container für mehrere Threads?
 
Zitat:

Zitat von Tyrael Y.
Dein Problem kannst du sehr einfach mit CriticalSections lösen.

Das ist ja im Container bereits untergebracht :zwinker:

TheMiller 28. Feb 2008 14:49

Re: Mehrere Container für mehrere Threads?
 
Hm...

mit der Erstellung des Containers habe ich es so gemacht:

Delphi-Quellcode:
var
  Form4: TForm4;
  Container: TContainer;

procedure TForm4.FormCreate(Sender: TObject);
begin
  Container:=TContainer.Create;
end;

procedure TForm4.FormDestroy(Sender: TObject);
begin
  Container.Free
end;
Der Grund ist folgender: Die Prozedur StartThread startet den Thread und weist ihm den Container zu. Die Methode CallBack empfängt die Nachricht des Thread, dass er Daten in ExpData geschrieben hat und ruft dann eine entsprechende Prozedur auf, die die Daten anzeigt. Also habe ich zwei Prozeduren, die auf die Daten von "Container" zugreifen und daher Global als Variable von Form4 definiert. Ist das jetzt richtig oder falsch?

sirius 28. Feb 2008 14:57

Re: Mehrere Container für mehrere Threads?
 
Du musst eigentlich keine Variable global definieren. Lege sie doch als Feld in TForm4 hinein.

Startthread und Callback sehe ich nicht.

TheMiller 28. Feb 2008 15:03

Re: Mehrere Container für mehrere Threads?
 
Erklär mir das bitte nochmal genauer...

Hier nochmal die Methoden für's absenden des Threads und für's Empfangen der Nachrichten:

Delphi-Quellcode:
procedure TForm4.StartThread;
var
  SQL: TSQLThread;
begin
  SQL:=TSQLThread.Create(True);
  SQL.FreeOnTerminate:=True;
  SQL.postvars:=False;
  SQL.FHndl:=Form4.Handle;
  SQL.DataCode:=1; // Identifiziert LParam, damit ich mit case of arbeiten kann
  SQL.DataContainer:=Container;
  SQL.Resume;
end;

procedure TForm4.CallBack(var msg: TMessage);
begin
  if (msg.WParam = 1) then
  begin
    // LParam => Container.DataCode (also mein Identifizierungsmerkmal)
    case (msg.LParam) of
      1: Machwasmitdaten1;
      2: Machwasmitdaten2;
    end;
  end;
end;
Achso, und "Machwasmitdaten1" und "Machwasmitdaten2" machen GANZ unterschiedliche Dinge, greifen aber beide auf Container.ExpData[x] zu.

sirius 28. Feb 2008 15:13

Re: Mehrere Container für mehrere Threads?
 
Jo ist doch alles super programmiert.
Da brauchst du überhaupt keine globale Variable. Leg sie doch einfach in die Klasse TForm4 rein.

So und jetzt musst du dir halt überlegen, ob du für jeden Thread einen eigenen Container machst, oder in den einen Container mehrere TExpDatas.
Wann Freigeben? gute Frage:
1. Du schickst eine Message am Ende des SQL-Thread an den Mainthread, der den Container löschen soll
2. Du löschst den Container im onTerminate-Ereignis des Threads (geht nur, wenn du nicht in einer DLL bist; ist aber ansonsten mit Variante 1 gleichwertig)
3. Du nimmst Interfaces. Die Löschen sich selber, wenn sie nicht mehr benötigt werden (Das Interface musst du wahrscheinlich von IReadWriteSync ableiten)

TheMiller 28. Feb 2008 15:16

Re: Mehrere Container für mehrere Threads?
 
Danke für das Kompliment - hast mir aber auch dabei recht viel geholfen!

Doch das verstehe ich immernoch nicht, wie meinst du das und wie kann ich dann mehrere Instanzen von "Container" haben

Zitat:

Da brauchst du überhaupt keine globale Variable. Leg sie doch einfach in die Klasse TForm4 rein.

sirius 28. Feb 2008 15:25

Re: Mehrere Container für mehrere Threads?
 
Eine Sache sehe ich grade, lass mal das "Form4" vor dem ".Handle" weg.

Variablen gehören entweder als Feld in eine Klasse oder lokal in eine Methode:
Delphi-Quellcode:
type Txy=class(...)

     private
       FConatiner:Tcontainer; //Das ist ein Feld einer Klasse (deswegen so ein F davor)
     ...
     public
       //oder hier, dann kann man auch von außen drauf zugreifen, brauchst du aber nicht
end;

mehrere Instanzen:
Delphi-Quellcode:
type Txy=class(...)

     private
       FConatiner:array of Tcontainer; //Das ist ein Feld einer Klasse (deswegen so ein F davor)
     ...
     public
       //oder hier, dann kann man auch von außen drauf zugreifen, brauchst du aber nicht
end;
Wobei eine TObjectList anstatt des Arrays bedeutend besser wäre. Und jetzt kannst du für jeden Thread ein Element im Array füllen.

TheMiller 28. Feb 2008 15:28

Re: Mehrere Container für mehrere Threads?
 
Ich depp :wall:

Jetzt weis ich wie du das gemeint hast. So mache ich es doch auch mit meinen PlugIns etc.

Ich werde es mal ausprobieren.

Zu dem Form4.Handle. Was wäre denn der Unterschied, außer, dass einmal Form4 fehlt und einmal nicht :wink:

sirius 28. Feb 2008 15:36

Re: Mehrere Container für mehrere Threads?
 
Zitat:

Zitat von DJ-SPM
Zu dem Form4.Handle. Was wäre denn der Unterschied, außer, dass einmal Form4 fehlt und einmal nicht :wink:

Der Unterschied ist derzeit (bzw. wenn du nur ein Form4 hast) Null.

Dass die VCL diese "blöde" globale Variable dahinsetzt, ist total besch****. Das verleitet jeden Anfänger dazu, sie zu benutzen.
Es ist ja zum Beispiel kein Problem aus TForm4 zwei Formulare zu instanzieren. Und du würdest immer nur das HAndle aus dem Formular bekommen, welches in der globalen Variablen Form4 gespeichert ist. Du bekommst Form4 automatisch mit in deine Methode (es heißt dann nur self und kann weggelassen werden). Deswegen kannst du ja problemlos aus einer Methode auf alle Felder des Objektes zugreifen. Und genau das amcht auch den Unterschied von Methoden zu einfachen Funktionen/Prozeduren aus.

TheMiller 28. Feb 2008 15:38

Re: Mehrere Container für mehrere Threads?
 
Achso. Ja, aber das mache ich ja nicht. Aber gut, ich werde es ändern und versuchen mir anzugewöhnen. Kann mir irgendwann vielleicht mal mehrere Stunden Suche sparen.

Ich spiele jetzt noch ein bissl mit meinen Containern rum!

Ich danke dir jedenfalls erstmal ganz viel! :thumb:

TheMiller 28. Feb 2008 16:02

Re: Mehrere Container für mehrere Threads?
 
Hey!

Noch eine Frage: Ich habe nun die Liste erstellt und folgendermaßen ein Object (TContainer) angefügt:

Delphi-Quellcode:
var
  SQL: TSQLThread;
  Data: TContainer;
  ContId: Integer;
begin
  Data:=TContainer.Create;
  ContId:=FContainerHolder.Add(Data);
  SQL:=TSQLThread.Create(True);
  SQL.FreeOnTerminate:=True;
  SQL.postvars:=False;
  SQL.FHndl:=Handle;
  SQL.DataCode:=1;
  SQL.DataContainer:=TContainer(FContainerHolder.Items[ContId]);
  SQL.Resume;
Aber woher weis meine CallBack-Funktion bzw. die Prozedure, die die Daten behandeln soll, aus welchem Container sie sie nehmen soll? Ich könnte es mehr Message machen, aber WParam und LParam sind leider schon belegt. Was mach ich jetzt?

wicht 28. Feb 2008 18:21

Re: Mehrere Container für mehrere Threads?
 
Wozu benutzt du WParam und LParam denn?
Ich denke, du könntest dir einen Record definieren, den in den Speicher packen, bei SendMessage einen Zeiger dahin mitschicken und dann beim "Empfänger" den Record einlesen, und nach SendMessage im "Sender" den Speicher wieder freigeben.
Und in diesem Record würdest du dir Felder definieren, die alle Infos über die "Nachricht" enthalten.
Oder halt einen Zeiger auf das Container-Objekt oder irgendwie so, da sollten einem doch keine Grenzen gesetzt sein?

HTH

Edit: Sehe gerade, die erste Frage ist wohl vermutlich überflüssig...

TheMiller 28. Feb 2008 18:24

Re: Mehrere Container für mehrere Threads?
 
Ich habe WParam dafür benutzt um zu identifizieren, ob ich Daten senden oder empfangen möchte. Ich habe aber jetzt zwei Messages gemacht, WM_GETCALLBACK und WM_POSTCALLBACK.

Dadurch wurde WParam frei und ich konnte es mit der Container-ID belegen.

Danke für eure Hilfe!

sirius 28. Feb 2008 19:32

Re: Mehrere Container für mehrere Threads?
 
Ansonsten kannst du auch überlegen, ob du den gesamten Bereich von wparam oder Lparam brauchst. du kannst ja ansonsten das hi- und loword der Parameter mit verschiedenen Zahlen belegen. Oder du änderst die Messagenummer.

TheMiller 28. Feb 2008 19:50

Re: Mehrere Container für mehrere Threads?
 
Hm, so passt das eigentlich super. WParam ist einfach die ContainerID und LParam ist die Indexnummer, welche Prozedur aufgerufen werden soll (Case of). Mehr brauche ist nicht. Und wie gesagt. Ich habe eine Message für den Datenempfang und die Anzeige und eine Message für's Daten senden und deren Rückgabewert (Erfolg, Fehlercode etc)

Nur interessehalber: Was ist das hi- und loword der Parameter?

sirius 28. Feb 2008 20:18

Re: Mehrere Container für mehrere Threads?
 
Ich habe mir schon gedacht, dass es so passt. Nur für den Fall, dass du es mal anders benötigst.

Du kannst doch bei TMessage wparamHi und wapramLo nutzen. Das sind halt von dem 32bit-Wert die oberen bzw. die unteren 16Bit.
Zusammensetzen auf der Send-Seite kannst du es mit
Delphi-Quellcode:
wparam:=(hiword shl 16)+loword;
postmessage(...,wparam,...);
//und dasselbe geht auch für Lparam.

TheMiller 28. Feb 2008 20:24

Re: Mehrere Container für mehrere Threads?
 
Gut - jetzt werde ich es nicht brauchen. Vielleich aber später mal. Finde die jetzige Lösung eigentlich sau gut :wink:

Jedenfalls vielen Dank. Habe ziemlich viel gelernt!


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:17 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz