Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Listen kontinuierlich aktualisieren (https://www.delphipraxis.net/182974-listen-kontinuierlich-aktualisieren.html)

michele_tedesco 1. Dez 2014 12:22

Listen kontinuierlich aktualisieren
 
Hallo Zusammen

Ich habe eine Client/Server Anwendung von Delphi 2007 zu Delphi XE5 gezügelt.

Eine Client-Funktionalität erlaubt es mehrere "Listen-Forms" (Forms mit einer StringGrid) zu öffnen, welche über eine ClientSocket gespeist werden.
Jede Liste enthält ca. 5 Spalten. Die Daten werden einmalig pro Client beim Starten des Client in eine Array von Records Daten-Struktur geladen.

Die Forms, welche in einem Client geöffnet sind, werde in einer TList verwaltet.

Wenn nun über eine TCP-Nachricht der Server eine Nachricht sendet, werden alle Clients benachrichtigt. Jeder Client interpretiert die Nachricht und in einer Schlaufe aller geöffneten Forms (TList) werden alle StringGrids Zeile für Zeile aktualisiert.
Dieses Update passiert in Sekunden-Takt. Es werden in ca. 2 Stunden ca. 30'000-Einträge gemacht.

Bei 4 Clients mit je 5 göffneten Forms, sind es 20 Forms mit einer StringGrid die pro TCP Nachricht aktualisiert werden.

Wenn diese Clients auf D2007 kompiliert werden, dann kann es schon sein dass die CPU für 2,3 Mal 1-2 Sekunden 80-90% CPU Last erzeugen. Das ist immer dann, wenn am meisten TCP-Nachrichten dicht bei einander gesendet werden.

Wenn ich die selbe Anwendung nun auch DXE5 kompiliere, ergeben sich am Anfang die ähnlichen CPU-Peaks, nur erholt sich die CPU dann bis am Ende der Verarbeitung nicht mehr und die Clients sind dann "wie eingefroren". Da die CPU-Last so weit oben ist, dass das UI nicht mehr Antwort gibt.

Wie würde man solch ein Problem heute in Delphi XE5,6 oder 7 lösen?

Danke und Gruss

Dejan Vu 1. Dez 2014 12:45

AW: Listen kontinuierlich aktualisieren
 
Optimierungspotential:
1. Die Nachrichtenverarbeitung per TCP im Hintergrund laufen lassen. Auch die Listen werden im Hintergrund aktualisiert, allerdings nicht(!) die Darstellung
2. Ein Timer aktualisiert z.B 2x pro Sekunde die Listen.
3. Die Listen sind gegen kongruete Zugriffe geschützt (TCritical Section)
4. Der Aktualisierungstimer wird nur gestartet, wenn die Liste verändert wurde.
5. Es werden nur die Zeilen im StringGrid aktualisiert, die sich auch verändert haben.
6. Aktualisierungen im StringGrid mit 'BeginUpdate'/'EndUpdate' umschließen.
7. Nur sichtbare Zeilen aktualisieren.
8. Statt StringGrid eine ListView (oder gleich ein VST) im virtual Mode nehmen.
Damit sollte die Skalierbarkeit deutlich erhöht werden.

p80286 1. Dez 2014 16:35

AW: Listen kontinuierlich aktualisieren
 
@Dejan Vu
Noch 11 und Du hast einen Grund zu feiern!

@michele_tedesco
abgesehen von den Vorschlägen, die Dejan Vu gemacht hat, sollte das Verhalten eigentlich gleich/ähnlich sein. Wie sieht es mit den Compilerschaltern aus (Range Check etc.)

Oder hast Du vielleicht zu viele (implizite) Wechsel/Konvertierungen zwischen AnsiString und (wide)String eingebaut?

Gruß
K-H

Der schöne Günther 1. Dez 2014 18:05

AW: Listen kontinuierlich aktualisieren
 
Nachdem ich bei der Suchmaschine meines Vertrauens einmal "delphi stringgrid beginupdate" eingegeben habe scheint es, dass der TStringGtrid selbst extrem langsam ist- Zumindest wenn man neue Zeilen hinzufügst.

Angeblich bringt es schon sehr viel, mittels einer WM_SETREDRAW das Ding einzufrieren und erst nachdem alles fertig ist wieder aufzutauen.

himitsu 1. Dez 2014 18:41

AW: Listen kontinuierlich aktualisieren
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1281867)
Nachdem ich bei der Suchmaschine meines Vertrauens einmal "delphi stringgrid beginupdate" eingegeben habe scheint es,

Das BeginUpdate ist dort auch besonders schön "versteckt".
http://www.delphipraxis.net/182881-c...ml#post1281804

Dejan Vu 1. Dez 2014 20:41

AW: Listen kontinuierlich aktualisieren
 
Zitat:

Zitat von p80286 (Beitrag 1281849)
@Dejan Vu
Noch 11 und Du hast einen Grund zu feiern!

Ich hab auch so einen Grund, zu feiern. Aber wie kommst Du auf 11?

Das Problem ist hier TCP bzw. das ständige Redraw der Grids.
Ich hatte Ähnliches mit 10 Threads, die einkommende TCP-Messages in 10 TMemos geloggt haben. Alles schön entkoppelt und ohne Synchronize, dafür mit Messages. Also imho optimal (halbwegs), jedenfalls besser als mit 'Synchronize' . Trotzdem fror die Anwendung immer wieder ein. Grund: Der Hauptthread wurde so dermaßen mit redraw-messages (und meinen eigenen) geflutet, das für normale Aktionen keine Zeit mehr war.

Ich habe dann einfach das ständige Aktualisieren der Memos deaktiviert. Wenn tausende Messages pro Sekunde ankommen, ist es doch wurscht, ob ich die alle sehe. Also: Daten in einen Ringbuffer und 2x pro Sekunde aktualisieren. Fertig.

Sir Rufo 1. Dez 2014 20:53

AW: Listen kontinuierlich aktualisieren
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ja wie kommt der auf 11, wo es doch nur noch 10 sind ;)
Anhang 42227

Dejan Vu 2. Dez 2014 07:03

AW: Listen kontinuierlich aktualisieren
 
:wall: Noch ein paar sinnlose Beiträge mehr und ich kann mir endlich die Pappnase und das Hütchen aufsetzen :party:

michele_tedesco 4. Dez 2014 08:14

AW: Listen kontinuierlich aktualisieren
 
Hallo Zusammen

@DejanVu und @himitsu, vielen Dank.
Ich habe bereits mehr als ein Optimierungschvorschlag eingebaut/umgebaut und es scheint schon Mal weniger Resourcen zu benutzen :cheer:

Vorallem BeguinUpdate und der Aktualisierungs-Timer haben sehr geholfen!

Gruss

generic 5. Dez 2014 10:01

AW: Listen kontinuierlich aktualisieren
 
Zitat:

Zitat von michele_tedesco (Beitrag 1281806)
Client interpretiert die Nachricht und in einer Schlaufe aller geöffneten Forms (TList) werden alle StringGrids Zeile für Zeile aktualisiert.
Dieses Update passiert in Sekunden-Takt. Es werden in ca. 2 Stunden ca. 30'000-Einträge gemacht.

Klingt nach vielen Daten...

Denk mal über einen Ansatz nach, dass *nur* die Änderungen möglichst kompakt übertragen werden.
Wenn du für jede Zeile ein TCP-Paket schickst, wird das sicherlich viel unnötigen Traffic machen.

Vorschlag:
Serverseitig machst du an jedem Datensatz Integer-Feld dran - nennen wir es Transaktion.
Der Server hat eine Transaktionsnummer. Immer wenn eine Aktualisierung an Daten durchgeführt wird, wird anschließend diese Nummer erhöht.
Die aktuelle Nummer wird an allen *veränderten* Datensätzen in das neue Feld geschrieben.

Startet nun ein Client, sagt er dem Server gibt mir alle Zeilen welche eine Transaktionsnummer größer 0 haben. Dieses entspricht einen vollen Ladevorgang -> Erstbefüllung.

Die höhste Transaktionsnummer der Daten merkt sich der Client.

Bei den nächsten (Teil-) Aktualisierung fragt der Client den Server nach allen Datensätzen/Zeilen die eine Transaktionsnummer haben, welche größer als die gemerkte ist.

Mit dieser Technik kannst du nur Teile übertragen und die Pakete etwas effizienter Nutzen.
Der Client muss weniger Daten verarbeiten, da nicht immer ein Full-Update durchgeführt wird.
Wenn keine Daten verändert wurden, werden keine Daten übertragen.

Alternativ könnte man über Multicast, MessageQueues nachdenken.

Sir Rufo 5. Dez 2014 10:29

AW: Listen kontinuierlich aktualisieren
 
Noch "einfacher" geht es, wenn man zwischen Datenbank und Anwendung eine weitere Schicht einbaut, die genau das regelt.

IdR gibt ein UPDATE-Statement mit gleichen Feldwerten ein RowsAffected mit 0 zurück. Die Zwischenschicht kümmert sich also um das Eintragen und auch wenn ein UPDATE ausgeführt werden soll, dann kann man trotzdem unterscheiden ob wirklich eine Änderung vorliegt und dann entsprechend benachrichtigen.

Die Anwendung selber spricht dann mit der Zwischenschicht und kann sich von der benachrichtigen lassen (Push) oder eben auch nachfragen (Poll), ob sich etwas verändert hat und vor allem was.

Die Zwischenschicht muss auch nicht zwangsläufig Delphi sein.

Dejan Vu 5. Dez 2014 11:56

AW: Listen kontinuierlich aktualisieren
 
Es gibt aber auch Anwendungen, da gehen mal eben tausende von Messages durch, das sind dann keine redundanten Meldungen ("Bei mir tut sich nix").

Beispiel: Ein MES-Interface verbindet eine Maschine in der Produktion mit dem Leitrechner. Wenn sich in der Maschine etwas tut, soll es eine Nachricht an das MES schicken. Das passiert z.B. 1x pro Sekunde ("Stempel xy gedrückt, Bauteil SN 12345 prozessiert etc,")

1. Fall: Es gibt 1000 Maschinen in einer etwas größeren Fabrik, also hab ich kontinuierlich 1000 Messages pro Sekunde. Da kann ich mit Schichten arbeiten, oder nur Änderungen verschicken, es bleiben einfach 1000 Messages pro Sekunde.

2. Fall: Die Verbindung zum MES ist unterbrochen. Das MES benötigt aber alle Nachrichten. Also puffert das Interface die Daten und sobald die Verbindung wieder steht, werden die Daten nachträglich rübergeblasen. Natürlich muss das schneller gehen als 1x pro Sekunde und die Daten sollen ja auch möglichst schnell wieder in Echtzeit abgeleifert werden, also fluten diese Nachrichten den Empfänger.

Im 2.Fall habe ich z.B. nur 10 Queues, die mir im Normalfall die Nachrichten pro Maschine anzeigen. Logischerweise ganz einfach als 10 Grids mit jeweils sekündlichem Update. Ist ja kein Ding.... nur wenn dann die Verbindung mal unterbroche wurde, und dann anschließend die gepufferten Daten rüberkommen, habe ich kurzzeitig z.B. pro Maschine 100 Nachrichten pro Sekunde. Und dann geht mein eigentlich vollkommen ausreichendes Nachrichtenanzeigesystem für mehr oder minder kurze Zeit in die Knie.

Auch hier würde eine Anfrage "Was hat sich denn geändert?" zur Antwort:"50.598 Zeilen" führen, was widerum ein Anzeigeproblem verursacht.

Die Überlegung ist doch die: Wozu dient die Anzeige? Will man wirklich alles in Echtzeit sehen oder will man doch eigentlich nur sehen, das etwas passiert (und im Groben auch: WAS passiert)?

Natürlich sollte man Empfang und Speicherung einerseits und die Darstellung andererseits trennen, vielleicht oder mit Sicherheit sogar auch hardwaretechnisch. An einen 24/7 Server werden nun einmal andere Ansprüche gestellt als an eine Leitwarte.

Sinnvollerweise würde ich die Speicherung in einem SQL-Server vornehmen, womit sich die Zwischenschicht schon erschlagen hat, denn das kann das Teil ja von alleine. Auch Notifications lassen sich mit den meisten RDBMS erledigen. Aber auch hier gilt: Bloß die Anzeige nicht fluten.

Wenn man nun die Daten in einem DB-Grid darstellt und das DB-Grid fragt einfach 2x pro Sekunde "Select top 20 * from DatenTabelle order by Zeitstempel", dann ist vollkommen egal, wie viele Daten in die Tabelle reingepustet werden. Ich bekomme 2x pro Sekunde die aktuellsten 20 Sätze. Ob die sich ändern oder nicht oder ob zwischen zwei Abfragen 750 Mio neue reingekommen sind, ist wurscht. Wichtig ist nur, das ein geeigneter Index auf der Zeitstempeltabelle ist, damit das sortieren nichts kostet.

Alternativ dazu kann das RDBMS auch einen Statussnapshot der Maschinen generieren (per Trigger) und die Clients fragen diesen Snapshot einfach ab. Wieder wird keine Zwischenschicht benötigt.

Ich stehe auf dem Standpunkt, das ich den Grad der Komplexität in einem System nicht unnötigerweise erhöhen sollte. Und zusätzliche Dienste, Schichten etc. können diesen Grad eben erhöhen (Und sie tun dies in den meisten Fällen auch): Noch eine Konfig, noch ein System, was crashen und Probleme verursachen kann etc.

Natürlich: Habe ich 100te unterschiedlicher und frei konfigurierbarer "Maschinen" bzw. Statusse (nein: nicht Stati), die ich pflegen muss, werde ich einen Teufel tun, das in der DB mit Triggern zu bewerkstelligen denn da ver X-fache ich meinen Komplexitätsgrad gerade durch das *Weglassen* von zusätzlichen Dienern.


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:20 Uhr.

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