Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Liste (ähnlich dyn Array) mit fortlaufendem Index? (https://www.delphipraxis.net/149577-liste-aehnlich-dyn-array-mit-fortlaufendem-index.html)

moelski 24. Mär 2010 14:25


Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

Gibts eigentlich eine Liste mit einer Art fortlaufendem Index?

Bsp: In SQL gibts ja den Feldtyp AutoInc. Bedeutet das Feld bekommt immer einen Wert MAX+1 und das unabhängig davon ob ich vorne in der Liste etwas lösche oder nicht. Der Index is immer eindeutig und ich kann damit einen Datensatz eben auch eindeutig identifizieren (u.a.).

Gibt es sowas auch in Delphi als fertiges Objekt? Kann das evtl. TQueue oder TCollection?

Hintergrund:
Ich möchte eine Liste aufbauen die ich dynamisch ergänze. Aus dieser Liste wiederum möchte ich Indexbezogen Daten asynchron auslesen und verarbeiten und dann die Daten aus der Liste löschen.
Wenn ich jetzt z.B. ein dyn. Array dafür verwende, dann habe ich keinen eindeutigen Index auf den ich zugreifen kann, denn durch das löschen verschiebt sich alles immer wieder.
Das Löschen wiederum ist wichtig weil ich die Daten nicht unnötig im Speicher halten möchte.


:gruebel:

mkinzler 24. Mär 2010 14:37

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Halte den "Index" als Teil der Daten

Uwe Raabe 24. Mär 2010 14:40

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Bei TCollection haben die Items eine eindeutige ID property, die beim Einfügen in die Collection gesetzt wird. Der Index kann sich ändern, die ID nicht.

Khabarakh 24. Mär 2010 14:42

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
So etwas habe ich ehrlich gesagt noch nie gebraucht, schließlich stellt doch der Pointer auf das Objekt selbst schon eine eindeutige ID dar :?: Einziger Unterschied ist, dass die Objekt-Adressen natürlich nicht unbedingt aufsteigend sind.

himitsu 24. Mär 2010 14:42

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Zitat:

Zitat von mkinzler
Halte den "Index" als Teil der Daten

Genau,

der Index für den Zugriff auf die Daten wäre dann nicht der "Index" der Daten innerhalb der Datenhaltung, sondern der Wert eines Feldes innerhalb der einzelnen Daten.

Beim Zugriff müßten dann nur die Daten mit dem entsprechende Indexwert gesucht werden ... ähnlich .Name bei einer Stringliste.

Blup 24. Mär 2010 16:34

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Warum sollte das Objekt selbst einen festen Index haben?
Wird das Objekt irgendwo referenziert, kann ich dort direkt auf die Felder zugreifen.
Die Position in einer Liste mit IndexOf(Object).
Falls ich wirklich so etwas wie eine ID brauch, dann die Adresse im Speicher Cardinal(Pointer(Object)).
Allerings kann ein neues Objekt dann eventuell die Speicheradresse eines bereits freigegebenen Objektes bekommen.

moelski 24. Mär 2010 21:22

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

Erstmal danke für die Antworten bis hierher.
Zitat:

Beim Zugriff müßten dann nur die Daten mit dem entsprechende Indexwert gesucht werden
Genau das möchte ich eben vermeiden, das ich in den Daten noch rumsuchen muss.

Ich möchte eben an der Liste Daten anfügen z.B. mit einem Thread. Und parallel dazu möchte ich Teile der Daten auswerten lassen. Dazu wäre es eben sehr praktisch einen fortlaufenden festen Index zu haben. Denn so könnte ich der auswertenden Routine sagen "Bearbeite mal Index 34-103" ohne mir jemals Gedanken machen zu müssen ob diese Index auch wirklich nach dem Start der Bearbeitung weiterhin so existieren.

Denn wie gesagt möchte ich die Bereiche der Liste die bearbeitet worden sind löschen.

Zitat:

Bei TCollection haben die Items eine eindeutige ID property
Da hätte ich aber nicht den Zugriff direkt über einen Index sondern müsste ja letztlich auch suchen. Sicher machbar, aber wie zuvor geschrieben möchte ich Suchen vermeiden aus Performancegründen.

Tryer 24. Mär 2010 22:51

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Ich habe das Problem mal wie folgt gelöst:

Beide Threads ("Sammler" und "Arbeiter") haben je eine TList.
Der Sammler füllt fleissig seine Liste und setzt einen Event über den der Arbeiter erkennt das neue Daten vorliegen (WaitForSingleObject). Dann tauscht er seine abgearbeitete, leere Liste per
Delphi-Quellcode:
FWorkList := TList(InterlockedExchange( Integer(FSammler.FList), Integer(FWorkList)));
mit dem Sammler aus.
Der Sammler macht immer ein einfaches Add() und der Arbeiter kann von 0 bis Pred(Count) zugreifen ohne das sich die beiden jemals ins Gehege kommen.
Der eindeutige Index ist dann überflüssig.

MfG,
Dirk

moelski 25. Mär 2010 08:14

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin Tryer,

das klappt bei mir leider nicht so ganz.
Denn bei mir müssen zwei "worker" aus der Liste lesen - ggf. sogar mehr.

Ich habe es jetzt mal mit TList versucht.
Meine Hoffnung war das ich dort Pointer einhängen kann. Das klappt auch.
Danach habe ich die Einträge der Liste auf NIL gesetzt. Aber es wird kein Speicher freigegeben.
Das passiert vermutlich nur bei Delete.

Hmm scheint so als müsse ich doch einen eigenen Index mitführen.
Das passt mir zwar irgendwie gar nicht weil ich dann suchen muss, aber mal sehen ...

himitsu 25. Mär 2010 08:21

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Zitat:

Genau das möchte ich eben vermeiden, das ich in den Daten noch rumsuchen muss.
Suchen wirst du so oder so müssen, es sei denn du nimmst die ID doch direkt als Index und erzeugst für alle nichtvorhandenen IDs leere Einträge.

Und das Suchen würde natürlich in das "Array"-Objekt integriert.
Halt so ähnlich, als wie es bei StringListe.Name[] gelöst ist.

moelski 25. Mär 2010 08:35

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

Zitat:

es sei denn du nimmst die ID doch direkt als Index
wie würde ich das tun ?

Zitat:

und erzeugst für alle nichtvorhandenen IDs leere Einträge
Würde das aber nicht trotzdem Speicherverbrauch bedeuten für die leeren Einträge?

Tryer 25. Mär 2010 08:42

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Zitat:

Zitat von moelski
Danach habe ich die Einträge der Liste auf NIL gesetzt. Aber es wird kein Speicher freigegeben.
Das passiert vermutlich nur bei Delete.

"Passieren" müsste die Freigabe natürlich durch den Arbeiter wenn er mit dem Element fertig ist. Aber bei mehr als einem Arbeiter bietet sich eine bessere (weil nicht starr im Speicher ausgerichtete) Lösung an: Die gute alte einfach verkettete Liste. Der Sammler hängt ans letzte Element an und die Arbeiter nehmen sich immer das erste Item aus der Liste. Der Sammler erstellt das Item, der Arbeiter gibt es frei.

Grüsse,
Dirk

moelski 25. Mär 2010 08:54

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin Dirk,

Anbei mal mein Code.
Wie würde das ansatzweise aussehen wenn ich verkettete Listen verwende?

Delphi-Quellcode:
unit DataClass;

interface

uses Classes, SyncObjs,
     SysUtils, Contnrs;

type
  TOneData = record
      ID                            : Int64;
      Zeichenkette                  : string[255];
      Value1, Value2, Value3, Value4 : Double;
      Kill                          : Boolean;
    end;

  TDataClass = class(TPersistent)
  private
    { Private-Deklarationen }
    fDataMutex : TMutex;        // nur ein Thread darf Daten schreiben / löschen
    fNotify    : TNotifyEvent;

    fData      : Array of TOneData;

    fID        : Int64;   // eindeutiger Index
  public
    { Public-Deklarationen }
    constructor Create;
    Destructor Kill;

    Procedure Add(Name : String)         ; overload;
    Procedure Add(V1, V2, V3, V4 : Double); overload;
    procedure DeleteItem(ID : Int64);
    Function GetLength : Integer;
    procedure GetIDs(var firstID, lastID : Int64);
    Function Read(Item : Integer) : String;                     overload;
    procedure Read(var V1, V2, V3, V4 : Double; Item : Integer); overload;
    function Done(Item : Integer) : Boolean;
  published
    property OnNotify: TNotifyEvent read fNotify write fNotify;
  end;

implementation

constructor TDataClass.Create;
begin
  inherited;
  fDataMutex := TMutex.Create(nil, False, 'DataMutex', True);
  fID       := 0;
end;

Procedure TDataClass.Add(Name : String);
begin
  fDataMutex.Acquire;
  Inc(fID);
  SetLength(fdata, Length(fdata) + 1);
  fdata[Length(fData) - 1].Zeichenkette := DateTimeToStr(Now) + ' ' + Name;
  fdata[Length(fData) - 1].ID          := fID;
  fDataMutex.Release;

  // Notify auslösen ...
  if Assigned(fNotify) then fNotify(Self);
end;

Procedure TDataClass.Add(V1, V2, V3, V4 : Double);
begin
  fDataMutex.Acquire;
  Inc(fID);
  SetLength(fdata, Length(fdata) + 1);
  fdata[Length(fData) - 1].Value1 := V1;
  fdata[Length(fData) - 1].Value2 := V2;
  fdata[Length(fData) - 1].Value3 := V3;
  fdata[Length(fData) - 1].Value4 := V4;
  fdata[Length(fData) - 1].ID    := fID;
  fDataMutex.Release;
end;

procedure TDataClass.DeleteItem(ID : Int64);
begin
//  fdata
end;

Function TDataClass.GetLength : Integer;
begin
  fDataMutex.Acquire;
  Result := Length(fData);
  fDataMutex.Release;
end;

procedure TDataClass.GetIDs(var firstID, lastID : Int64);
var I : Integer;
begin
  fDataMutex.Acquire;
  for I := 0 to Length(fData) - 1 do begin
    if fData[I].Kill = False then
      Break;
  end;

  firstID := I;

  fDataMutex.Release;
end;

Function TDataClass.Read(Item : Integer) : String;
begin
  fDataMutex.Acquire;
  Result := fData[Item].Zeichenkette;
  fDataMutex.Release;
end;

procedure TDataClass.Read(var V1, V2, V3, V4 : Double; Item : Integer);
begin
  fDataMutex.Acquire;
  V1 := fData[Item].Value1;
  V2 := fData[Item].Value2;
  V3 := fData[Item].Value3;
  V4 := fData[Item].Value4;
  fDataMutex.Release;
end;

function TDataClass.Done(Item : Integer) : Boolean;
begin
  fDataMutex.Acquire;
  Result := fData[Item].Kill;
  fDataMutex.Release;
end;

Destructor TDataClass.Kill;
begin
  inherited;
  fDataMutex.Free;
end;

end.

Tryer 25. Mär 2010 10:00

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Speicherst Du entweder die Zeichenkette oder die Realwerte? Die Vermischung finde ich unübersichtlich, aber diesbezügich kannst Du das ganze ja selber anpassen.
"Mal eben", also garantiert nicht fehlerfrei:
Delphi-Quellcode:
type
  POneData = ^TOneData;
  TOneData = record
      Zeichenkette: string;
      Value1: Double;
      Value2: Double;
      Value3: Double;
      Value4: Double;
      _Next: POneData;
    end;

  TDataClass = class
  private
    FEvent: THandle;
    FLock: TRTLCriticalSection;
    FFirst: POneData;
    FLast: POneData;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(Name: string; V1, V2, V3, V4 : Double);
    procedure ExtractFirst: POneData;
    procedure Lock;
    procedure Unlock;
    property NewDataEvent: TEvent read FEvent write FEvent;
  end;

  TWorker = class(TThread)
  private
    FDataClass: TDataClass;
  protected
    procedure Execute; override;
  public
    constructor Create(DataClass: TDataClass);
  end;

implementation

constructor TDataClass.Create;
begin
  inherited Create;
  FFirst := nil;
  FLast := nil;
  FEvent := CreateEvent(nil,True,False,nil);
  InitializeCriticalSection(FLock);
end;

Procedure TDataClass.Add(Name : string; V1, V2, V3, V4 : Double);
var
  NewData: POneData;
begin
  New(NewData);
  NewData^._Next := nil;
  NewData^.Zeichenkette:= Name;
  NewData^.Value1 := V1;
  NewData^.Value2 := V2;
  NewData^.Value3 := V3;
  NewData^.Value4 := V4;
  try
    Lock;
    try
      if Assigned(FLast) then
      begin
        FLast^._Next := NewData;
        FLast := NewData;
      end else
      begin
        FFirst := NewData;
        FLast := FFirst;
      end;
      SetEvent(FEvent);
    finally
      Unlock;
    end;
  except
    Dispose(NewData);
  end;
end;

procedure TDataClass.Lock;
begin
  EnterCriticalSection(FLock);
end;

procedure TDataClass.Unlock;
begin
  LeaveCriticalSection(FLock);
end;

function TDataClass.ExtractFirst: POneData;
begin
  Result := nil;
  Lock;
  try
    ResetEvent(FEvent);
    if Assigned(FFirst) then
    begin
      Result := FFirst;
      FFirst := FFirst^._Next;
    end;
  finally
    Unlock;
  end;
end;

destructor TDataClass.Destroy;
var
  tmp: POneData;
begin
  while FFirst <> nil do
  begin
    tmp := FFirst;
    FFirst := FFirst^._Next;
    Dispose(tmp);
  end;
  CloseHandle(FEvent);
  DeleteCriticalSection(FLock);
  inherited Destroy;
end;

procedure TWorker.Execute;
var
  PData: POneData;
begin
  repeat
    if WaitForSingleObject(FDataClass.NewDataEvent, 100) = WAIT_OBJECT_0 then
    begin
      PData := FDataClass.ExtractFirst;
      if Assigned(PData) then
      begin
        try
          Verarbeite(PData^);
        finally
          Dispose(PData);
        end;
      end;
    end;
  until Terminated;
end;

constructor TWorker.Create(DataClass: TDataClass);
begin
  inherited Create(True);
  FDataClass := DataClass;
  Resume;
end;
Grüsse,
Dirk

EDIT: kleine Korrektur: Das SetEvent im Add() muss in den abgesicherten Bereich.

moelski 25. Mär 2010 11:40

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

der Code bringt noch ne Menge Fehler. So kann er mit dem TEvent so nichts anfangen.
Das kommt immer [DCC Fehler] Thread.pas(53): E2010 Inkompatible Typen: 'TEvent' und 'Cardinal'
oder umgekehrt

Das Thread Create ist wohl auch nicht ganz sauber, aber das bekomme ich schon hin.

Könntest du mir mal ganz kurz erklären welche Rolle TEvent generell spielt?
Sehe ich das richtig, dass das eine Art Notifikation an den Thread ist, das neue Daten vorhanden sind?

Und wie würde ich bei dem Konstrukt einen Worker aufsetzen der jede Sekunde nur jeweils den letzten eintrag der Kette liest?

Danke auf jeden Fall schon mal bis hierhin !

Tryer 25. Mär 2010 12:00

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Sorry, muss natürlich vom Typ THandle (bzw. Cardinal) sein. Und natürlich heißt es "constructor Create(..)" in der Implementierung. Schreibfehler halt ;)
Mit dem Event(siehe Hilfe zu CreateEvent) werden die gerade arbeitslosen, wartenden Worker aktiviert um sich die Daten abzuholen.

Das ganze ist natürlich so ausgelegt das der Worker sich einfach den Job holt der am längsten in der DataClass steht (halt "First"), und dass sobald er den letzten Job abgearbeitet hat. Den Worker könnte man natürlich über ein Sleep(1000) o.ä. pausieren lassen, aber darin kann ich den Sinn nicht erkennen.
Was soll denn da überhaupt und zu welchem Zweck verarbeitet werden?

Grüsse,
Dirk

himitsu 25. Mär 2010 12:03

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Zitat:

Zitat von moelski
Könntest du mir mal ganz kurz erklären welche Rolle TEvent generell spielt?
Sehe ich das richtig, dass das eine Art Notifikation an den Thread ist, das neue Daten vorhanden sind?

Jupp

Ich würde TEvent aber weglassen oder zumindestens Folgendes in eine Schleife legen, welche solange arbeitet, bis keine Daten mehr in der Liste stehen oder bis Terminated = True.
Delphi-Quellcode:
PData := FDataClass.ExtractFirst;
if Assigned(PData) then
begin
  try
    Verarbeite(PData^);
  finally
    Dispose(PData);
  end;
end;
Denn so würde die Liste nie vollständig abgearbeitet, wenn mal mehrere Einträge gleichzeitig in der Liste stehen, da immer nur ein Eintrag je "Signal" verarbeitet wird, da das Signal nach dem Entfernen eines Eintrages zurückgesetzt wird.

Tryer 25. Mär 2010 12:39

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Delphi-Quellcode:
  repeat
    if WaitForSingleObject(FDataClass.NewDataEvent, 100) = WAIT_OBJECT_0 then
    begin
      PData := FDataClass.ExtractFirst;
      if Assigned(PData) then
      begin
        repeat
          try
            Verarbeite(PData^);
          finally
            Dispose(PData);
          end;
          PData := FDataClass.ExtractFirst;
        until not Assigned(PData);
      end;
    end;
  until Terminated;
end;
?
Den Event würde ich drin lassen um die CPU zu entlasten wenn es gerade mal nichts zu tun gibt und ansonsten sofort loszulegen. Man könnte auch "INFINITE" warten, dann muss aber ins Destroy auf jeden Fall ein SetEvent um den Thread abzubrechen.

himitsu 25. Mär 2010 12:49

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
OK, wenn das Event drinnenbleiben soll und man keine zusätzliche Schleife möchte, dann halt das Event nur zurücksetzen, wenn nichts mehr in der Liste steht.
Delphi-Quellcode:
function TDataClass.ExtractFirst: POneData;
begin
  Lock;
  try
    if Assigned(FFirst) then
    begin
      Result := FFirst;
      FFirst := FFirst^._Next;
      if not Assigned(FFirst) then
        ResetEvent(FEvent);
    end else Result := nil;
  finally
    Unlock;
  end;
end;

moelski 25. Mär 2010 12:53

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

Dann will ich mal das ganze Kostrukt beschreiben um das es geht ...

Bei uns geht es um die Verarbeitung von (zumeist seriellen - RS232/USB) Daten. Diese Daten kommen z.B. von einem Datenlogger und liefern - sagen wir mal Spannung, Strom, Höhe.

Die Schnittstelle (bzw. die Komponente dazu) ist in dem Fall dann also der Datenlieferant und benutzt das TDataClass.Add um Daten anzulegen. Ob diese Daten dann schon umgerechnet sind von einem String hinzu 3 Floats sei an dieser Stelle mal unwichtig.

Wenn die Daten dann abgelegt sind, dann soll einmal die Sekunde alles was noch offen in der Kette ist in eine Grafik geschrieben werden.
Zusätzlich sollen zwei Threads einmal die Rohdaten (empfangene Strings) in eine Datei schreiben und einmal die umgerechneten Daten ggf. in eine Art CSV.

Dann könnte es noch sowas wie Analoganzeigen (Gauges) geben die eben jede 500-1000ms mal den letzten Wert der Kette darstellen sollen als eine Art "Livemonitoring".

Gelöscht werden müssen die Daten wenn die Daten in der Grafik gelandet sind und die beiden Dateien geschrieben sind.

Soweit mal die Idee hinter dem ganzen.

moelski 25. Mär 2010 13:14

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
So nochmal ich.

Der Code lässt sich nun schon mal übersetzen. Danke Dirk.
Funktioniert nur noch nicht so wie gedacht ... :)

Beim ersten Eintrag geht alles glatt. Das Event wird gesetzt, der Thread rennt an, er verarbeitet die Daten und macht das Dispose.

Wenn ich einen zweiten Eintrag hinzufüge macht der Thread aber nüscht mehr auch wenn das event abgefeuert wird.
Laufe tut der Thread allerdings wohl noch - sieht man ja im Debugger.

Zum Erzeugen der Klasse und des Thread nutze ich den Code:
Delphi-Quellcode:
var Form3 : TForm3;
    Kette : TDataClass;
    Work : TWorker;

implementation

procedure TForm3.Button1Click(Sender: TObject);
begin
  Kette.Add('Name', 1, 2, 3, 4);
end;

procedure TForm3.FormCreate(Sender: TObject);
begin
  Kette := TDataClass.Create;
  work := TWorker.Create(Kette);
  work.Start;
end;

himitsu 25. Mär 2010 13:23

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
OK, dann ist der fortlaufende Index für den Zugriff garnichtmal nötig.

Hier gibt es aber erstmal ein Problem:
Wenn jetzt zwei Threads immer alle Daten verarbeiten sollen und gleichzeigtig einer der Threads die Liste löschen würde.

Lösungen:
- entweder für jeden der Threads eine eigene Liste
- oder Beides in einem Thread lösen (also die beiden Dateien zusammen erstellen)

Die Abfrage der aktuellen Werte, für das Livemonitorin würde ich dann auch nicht über diese Liste machen, denn was willst du Anzeigen, wenn gerade in diesem Moment diese Liste noch/wieder leer ist?

moelski 25. Mär 2010 13:30

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Moin !

Zitat:

- oder Beides in einem Thread lösen (also die beiden Dateien zusammen erstellen)
Das wäre durchaus machbar.

Zitat:

Die Abfrage der aktuellen Werte, für das Livemonitorin würde ich dann auch nicht über diese Liste machen, denn was willst du Anzeigen, wenn gerade in diesem Moment diese Liste noch/wieder leer ist?
Ok da hast du Recht. Dafür sollte man die letzten Werte extra vorhalten. Aber erstmal musses ja funzionieren ;)

Ich denke der Code kommt noch ein bisserl durcheinander mit FFirst und FLast. Denn kann ja nicht sein das FLast noch einen Pointer beinhaltet obwohl der einzige dateneintrag gerade verarbeitet wurde:

himitsu 25. Mär 2010 13:47

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Wenn du die Live-Werte extra vorhälst, dann wird FLast nicht benötigt und eine stinknormale einfach verkettete Liste reicht vollkommen aus.

ach nee, für's Anhängen neuer Daten wird FLast ja dennoch benötigt.

Delphi-Quellcode:
uses
  Windows, Classes, SysUtils, SyncObjs;

type
  POneData = ^TOneData;
  TOneData = record
    Text: string;
    Value1: Double;
    Value2: Double;
    Value3: Double;
    Value4: Double;
    Next: POneData;
  end;

  TDataClass = class
  private
    FEvent: TEvent;
    FLock: TCriticalSection;
    FFirst, FLast: POneData;
    FLive: TOneData;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(const Name: string; const V1, V2, V3, V4: Double);
    function ExtractAll: POneData;
    function GetLive: TOneData;
    function WaitAndCheckForNewData(TimeOut: Cardinal): Boolean;
  end;

  TWorker = class(TThread)
  private
    FDataClass: TDataClass;
  protected
    procedure Execute; override;
  public
    constructor Create(DataClass: TDataClass);
  end;



constructor TDataClass.Create;
begin
  inherited Create;
  FEvent := TEvent.Create(nil, True, False, '');
  FLock := TCriticalSection.Create;
end;

destructor TDataClass.Destroy;
var
  tmp: POneData;
begin
  while FFirst <> nil do
  begin
    tmp := FFirst;
    FFirst := FFirst.Next;
    Dispose(tmp);
  end;
  FEvent.Free;
  FLock.Free;
  inherited Destroy;
end;

procedure TDataClass.Add(const Name: string; const V1, V2, V3, V4: Double);
var
  NewData: POneData;
begin
  New(NewData);
  NewData.Text := Name;
  NewData.Value1 := V1;
  NewData.Value2 := V2;
  NewData.Value3 := V3;
  NewData.Value4 := V4;
  NewData.Next := nil;
  try
    FLock.Acquire;
    try
      FLive := NewData^;
      if Assigned(FLast) then
      begin
        FLast.Next := NewData;
        FLast := NewData;
      end else
      begin
        FFirst := NewData;
        FLast := FFirst;
      end;
      FEvent.SetEvent;
    finally
      FLock.Release;
    end;
  except
    Dispose(NewData);
  end;
end;

function TDataClass.ExtractAll: POneData;
begin
  FLock.Acquire;
  try
    FEvent.ResetEvent;
    Result := FFirst;
    FFirst := nil;
    FLast := nil;
  finally
    FLock.Release;
  end;
end;

function TDataClass.GetLive: TOneData;
begin
  FLock.Acquire;
  try
    Result := FLive;
  finally
    FLock.Release;
  end;
end;

function TDataClass.WaitAndCheckForNewData(TimeOut: Cardinal): Boolean;
begin
  Result := FEvent.WaitFor(TimeOut) = wrSignaled;
end;

procedure TWorker.Execute;
var
  PData, PData2: POneData;
begin
  repeat
    if FDataClass.WaitAndCheckForNewData(100) then
    begin
      PData := FDataClass.ExtractAll;
      try
        while Assigned(PData) do
        begin
          PData2 := PData;
          PData := PData.Next;
          try
            Verarbeite(PData2^);
          finally
            Dispose(PData2);
          end;
        end;
      except
        // falls Exception, restlichen Speicher aufräumen und Werte verwerfen
        // aber es wäre auch möglich die Daten wieder in die Liste einzufügen
        // oder man "ignoriert" Exceptions und setzt einfach die obere Schleife fort
        while Assigned(PData) do
        begin
          PData2 := PData;
          PData := PData.Next;
          Dispose(PData2);
        end;
        Raise;
      end;
    end;
  until Terminated;
end;

constructor TWorker.Create(DataClass: TDataClass);
begin
  inherited Create(True);
  FDataClass := DataClass;
  Resume;
end;
Da hier gleich die ganze Liste aus der Datenhaltung rausgeholt wird, ist diese bereit sofort neue Daten aufzunehmen und wird weniger bei Auslesen blockiert.

Bei Verarbeite werden die Daten dann einfach in beide Dateien (Rohdaten und umgerechneten CSV-Daten geschrieben).
Die aktuellsten Live-Werte bekommt man über DataClass.GetLive geliefert.

moelski 25. Mär 2010 20:07

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

@himitsu:
Vielen Dank für deine Erweiterung. Das klappt nun erstaunlich gut :thumb:

Auch die Speicherbelastung bleibt wie erwartet niedrig. :)

Hätte dann noch ein paar wetere Fragen (und hoffentlich gehe ich euch nicht zu sehr auch den Sac* :roll: ).

1) Event an Thread
Wenn ich nun möchte das der Thread nur jede Sekunde die Daten verarbeitet, dann könnte ich doch einfach in TDataClass.Add bei FEvent.SetEvent eine Zeitliche Überprüfung einbauen.
Also sowas wie Millisecondsbetween(Now, Then) >= 1000 ... ?

2) Ich möchte ja die Daten auch in ein Chart schreiben. Und am liebsten würde ich die Daten natürlich so im Chart ablegen, dass der Hauptthread der Anwendung möglichst unbelastet bleibt.
Delphi-Quellcode:
  TWorker = class(TThread)
  private
    FDataClass : TDataClass;
    FChart    : TChart;
  protected
    procedure Execute; override;
  public
    constructor Create(DataClass: TDataClass; Chart : TChart);
  end;
So funktioniert das zwar im Thread, aber eigentlich müsste es ja über ein Synchronize laufen, oder? Aber das läuft ja dann wieder im Hauptthread. :gruebel:

3) Wie verhält sich TDataClass wenn der Hauptthread ausgelastet ist?
Derzeit erzeuge ich die Instanz von TDataClass ja im Hauptthread der Anwendung. Wenn die aber nun gerade zu 100% ausgelastet ist, was macht dann TDataClass? Dann würde doch ein Add nicht mehr zur Anwendung kommen, oder?
Das wäre natürlich fatal weil ich dann mitunter Daten verlieren könnte.
Könnte man TDataClass ggf. selber als Thread auslegen und somit vom HAuptthread abkapseln?

Tryer 26. Mär 2010 16:57

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Die Kommunikation mit der Peripherie sollte in einem eigenen Thread laufen welcher dann Add() aufruft. Immer wenn eine Funktion aufgerufen wird dann findet das immer im Kontext des aufrufenden Threads statt, also in einem anderen Thread als das Execute() läuft und unabhängig davon welcher Thread das Objekt erstellt hat. Solange der Kommunikationsthread (für den ich eine erhöhte Priorität empfehlen würde) zum Zuge kommt werden auch die Daten angenommen.

Da ich nicht weis wie schnell/gleichmäßig die Daten reinkommen ist es schwer zu sagen ob es sinnvoller ist eine Sekunde zu sammeln (GetTickCount o.ä.) oder von vornherein jedes Paket mit einem Zeitstempel zu versehen. Der Kommunikations-Thread könnte den Event "Daten vorhanden" z.B. nach jeweils 100 Paketen oder 1000ms setzen und der Worker übernimmt dann den ganzen Datensatz aus diesem Zeitraum (Das wäre dann vielleicht wieder die Variante mit dem Austauschen der ganzen Liste).

Mit TChart habe ich noch nicht gearbeitet, könnte mir aber prinzipiell vorstellen inn einem eigenen Thread ein Bitmap aufzubauen welches dann in einem kurzen Synchronize ausgegeben wird. Dieses Synchonize sollte auf keinen Fall aus dem Kommunikationsthread kommen damit dieser unabhängig von der Oberfläche arbeiten kann.

Grüsse, Dirk

moelski 27. Mär 2010 07:48

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

Zitat:

Die Kommunikation mit der Peripherie sollte in einem eigenen Thread laufen welcher dann Add() aufruft. Immer wenn eine Funktion aufgerufen wird dann findet das immer im Kontext des aufrufenden Threads statt
Bist du dir da sicher?

Sagen wir mal es gibt einen Kommunikationsthread und die Instanz der Datenklasse liegt im MainForm und damit ja eigentlich auch im MainThread.
Wenn ich nun das Add aus dem Kommu-Thread raus aufrufe, läuft das dann wirklich im Kommu-Thread ?

Zitat:

TChart & Bitmap
Das geht so nicht. Man muss die Daten schon im TChart eintragen um die Kurve zu erzeugen. Das macht die Sache nämlich zu kompliziert aus einem Thread heraus :(

@himitsu:
Hast du meine PN gelesen?

Tryer 27. Mär 2010 08:15

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Die Instanz liegt im Speicher, nicht im Threadkontext. Und alle deine Threads haben Zugriff auf den Speicher, man muss halt nur schauen das die Zugriffe geortnet ablaufen. Das wird über Synchronisierungsobjekte wie TCriticalSection oder das in TTread implementierte Synchronize durchgeführt. Synchronize legt prinzipiell die Adresse der Prozedur welche der Mainthread aufrufen soll in einer Liste ab und schickt eine Nachricht (WM_NULL) an den Mainthread das in der Liste Arbeit wartet. Bis der Mainthread das getan hat schläft der Thread, läuft also danach erst weiter. Deswegen ist Synchronize so eine Bremse welche man wirklich nur "wenn nötig" benutzen sollte. Verriegelungen die unabhängig vom Mainthread stattfinden können/dürfen sind also besser über die SyncObjekte zu realisieren.

TChart: Dann also die ValueLists für´s TChart aufbereiten und in einem Synchronize übergeben + zeichnen. Ist doch auch aus dem Thread kein Problem.

Grüsse, Dirk

moelski 29. Mär 2010 10:11

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Moin !

So ich habe jetzt mal alles in ein Projekt gegossen. Erstellt ist es mit D2010, aber ich denke es sollte auch mit anderen Delphi Versionen nutzebar sein.

Man kann mit dem Button "Add Input" an die verkettete Liste Daten anhängen. Das geht recht flott und der Output Thread arbeitet die Liste dann wieder ab.
Parallel gibt es einen Timer der jede Sekunde DatenKette.GetLive.Value1 ausgibt.

Ok, nun kommt aber der Spannende Teil ... Der TOutputThread ackert nun durch die Liste und jetzt müssen die Daten irgendwie mal in das Chart. Ich habe zu dem Zweck schon mal ein TChart Standard (ist bei jedem Delphi dabei) auf das Formular gepackt.
Ich habe dann testweise mal dem OutputThread das Chart übergeben:
Delphi-Quellcode:
constructor Create(DataClass: TDataClass); Chart : TChart);
Und dann im execute folgendes:
Delphi-Quellcode:
            fChart[0].Add(PData2.Value1);
            fChart[1].Add(PData2.Value2);
            fChart[2].Add(PData2.Value3);
            fChart[3].Add(PData2.Value4);
Das funktioniert, aber ich würde mal vermuten das es der falsche Weg ist, denn das wäre ja ein direkter Zugriff vom Thread auf das Chart :gruebel:

Zitat:

Dann also die ValueLists für´s TChart aufbereiten und in einem Synchronize übergeben + zeichnen
Tja da haben wir schon wieder das Problem ... Wenn ich Synchronize nutze, dann wird die Methide im Main Thread ausgeführt. Und genau das möchte ich vermeiden. Denn was passiert wenn der Main Thread (wegen anderer Berechnungen) hängt?

Blup 29. Mär 2010 10:48

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Zitat:

Zitat von moelski
Tja da haben wir schon wieder das Problem ... Wenn ich Synchronize nutze, dann wird die Methide im Main Thread ausgeführt. Und genau das möchte ich vermeiden. Denn was passiert wenn der Main Thread (wegen anderer Berechnungen) hängt?

Dann solltest du diese "Berechnungen" auch in einen Thread verschieben.
Wer mit der VCL arbeitet muss damit leben, nur der Hauptthread darf auf Steuerelemente zugreifen.

moelski 29. Mär 2010 10:54

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Moin !

Zitat:

Dann solltest du diese "Berechnungen" auch in einen Thread verschieben.
Naja, passiert ja auch schon alles im Thread.

Zitat:

Wer mit der VCL arbeitet muss damit leben
Bei einem TChart wird uns nichts anderes übrig bleiben, vermute ich mal :)

Zitat:

nur der Hauptthread darf auf Steuerelemente zugreifen
Je länger ich da überlege, desto mehr kommt mir die Frage ob das Synchronize mit dem MainForm nicht belanglos ist.

Die Daten werden mittels Thread abgeholt, verarbeitet und bereitgestellt. Und die Daten liegen in einer Liste vor die eben dynamisch anwachsen kann. Selbst wenn nun der MainThread mal blockt und somit die TChart Befüllung hängt, so baut sich im Hintergrund dennoch die Liste (dank Threads) weiter auf.
Und wenn der MainThread wieder "Luft" hat, würde ja auch der rest verarbeitet.

Oder sehe ich das gerade falsch ?

moelski 29. Mär 2010 13:55

Re: Liste (ähnlich dyn Array) mit fortlaufendem Index?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Moin !

So ich habe noch ein bisserl getestet. Habe die Demo auch auf MDI ausgebaut. Und das läuft wirklich gut.
Man kann enorme Datenmengen reinschaufeln und der Output Thread packt dann alles in das Chart nach und nach.

Also ich habe hier mal mit 500.000 Werten in knapp 5 Sekunden am Eingang getestet und das geht stressfrei über die Bühne. Bin echt beeindruckt. :thumb: Und das auch noch trotz Synchronize.

Ich habe auch mal additiv einen Button auf das MDI Main gelegt wo ich eine mehr oder minder sinnlose CPU Killerschleife laufen lasse. Und selbst dann funktioniert der Eingang sauber weiter. Und wenn die Last weg ist arbeiten die Output Threads auch wieder mit dem Chart. Wunderbar. Muss heute Abend nen Bier trinken auf euch :hi: :cheer: :cheers:

Werde dann heute noch den Input Thread durch eine reale Eingangsquelle ersetzen (RS232). Aber ich sehe da kein großes Problem. Zumal die Datenflut ja bei RS232 eher gering ist :-D

Falls jemand noch Verbesserungen zu vermerken hat ... Nur her damit.

Ich habe jedenfalls jetzt erstmal einen sehr brauchbaren Stand. Und ich möchte mich nochmal ganz herzlich bei Tryer & himitsu bedanken für die Umsetzung der verketteten Pointerliste. :dp:


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:35 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