AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Thema durchsuchen
Ansicht
Themen-Optionen

Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

Ein Thema von berens · begonnen am 19. Aug 2013 · letzter Beitrag vom 20. Aug 2013
Antwort Antwort
Furtbichler
(Gast)

n/a Beiträge
 
#1

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 07:05
Ursächlich für diese obskuren Fehler ist der Programmierstil. Er verstößt so ziemlich gegen jedes Gebot der robusten Programmierung: Die Routinen sind zu lang, es werden etwaige auftretende Fehler mit leeren Except-Blöcken einfach unter den Tisch gekehrt (Motto: "Habe keine Ahnung, was hier passiert"). Es wird zwar die Möglichkeit eines Abbruchs kommentiert, aber nirgens umgesetzt (if terminated then exit )

Im Code sehe ich 'FclProjekte'. Sag nicht, das das eine Listbox ist. Wenn ja => garantierte Fehlerquelle. Verwende *nie niemals nicht* ein VCL-Control als Datenstruktur. die VCL-Controls dienen *nur* zum Darstellen von Daten.
Delphi-Quellcode:
 
if blGefunden then begin
  FreeAndNil(tmpProjekt);
end else begin
  Synchronize(SyncNewDataset); // <--- was wird denn hier synchronisiert?
  FclProjekte.Add(tmpProjekt);
end;
Weiterhin erzeugst Du erst ein TProjekt, füllst es, suchst dann in der Liste nach, ob es schon da ist, schmeisst es wieder weg, wenn ja usw.

Das Suchkriterium ist die ID. Verwende eine Dictionary / Hashmap, um zu prüfen, ob Du die ID schon geladen hast. Wenn ja, gehe zum nächsten Record. Nur wenn die ID noch nicht geladene wurde, instantiierst du ein TProjekt, füllst die Eigenschaften und übergibst das an dein Formular. Weniger Klimmzüge => weniger Fehlerquellen.

Unterteile deine Methode in einzelne Methoden, die genau eine Aufgabe erledigen. Verwende *nie niemals nicht* leere Except-Blöcke, sondern logge alle Exceptions.

Zum Eingrenzen des Fehlers:

1. Kommentiere alles aus. Läuft es? => Weiter mit 2.
2. Aktiviere nur die ADO-routinen, d.h. instantiiere dein TProjekt, fülle es, schmeiss es weg. Läuft es? Weiter mit 3.
3. Aktiviere die Listenlogik (nur, wenn es keine Listbox ist). D.h. suche in der Liste usw. Läuft es? Weiter mit 4.
4. Aktiviere die Synchronize-Methoden eines nach dem anderen.
5. Tja, Keine Ahnung, wenn du hier gelandet bist.

Geändert von Furtbichler (20. Aug 2013 um 07:08 Uhr)
  Mit Zitat antworten Zitat
Blup

Registriert seit: 7. Aug 2008
Ort: Brandenburg
1.493 Beiträge
 
Delphi 12 Athens
 
#2

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 08:27
Im Code sehe ich 'FclProjekte'. Sag nicht, das das eine Listbox ist. Wenn ja => garantierte Fehlerquelle. Verwende *nie niemals nicht* ein VCL-Control als Datenstruktur. die VCL-Controls dienen *nur* zum Darstellen von Daten.
Der Call Stack lässt dies als Ursache vermuten.
Insbesondere der Zugriff aus einem Thread auf eine VCL-Komponente ist ein sicherer Absturzkandidat.
  Mit Zitat antworten Zitat
berens

Registriert seit: 3. Sep 2004
441 Beiträge
 
Delphi 10.4 Sydney
 
#3

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 08:35
Alle cl* Komponenten bei mit sind TComponentList. Von allen Datentypen aus der Datenbank jeweils eine, die NUR im Thread benutzt wird (Fcl*) und eine, die NUR außerhalb des Threads benutzt wird (cl*). Bei SyncNewDataset beispielsweise werden alle neu hinzugekommenen Einträge über Synchronize von Fcl* an cl* Übergeben.

Im Thread verwende ich keine VCL-Komponenten, ADO wurde mit CoInitialize etc. entsprechend vorbereitet.

Nochmal: Das komplette Programm läuft zuverlässig und einwandfrei. Das einzige was "stört" ist die Exception-Meldung ansich. Deshalb die leeren try..except Blöcke rund um allen Quelltext, den ich für den Bösewicht halte. Trotzdem kommt die Meldung weiterhin.

Wie kann ich denn von der Pointer-Adresse aus dem Fehlerlogbuch auf einen Variablennamen (z.B. FclProjekte) kommen, um zumindestens die schuldige Komponente benennen zu können? Ich sehe zwar, dass irgend eine Liste Probleme macht. Aber welche??

Danke schonmal für die Antworten. Ich hoffe wir finden was.
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#4

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 10:24
Nochmal: Das komplette Programm läuft zuverlässig und einwandfrei. Das einzige was "stört" ist die Exception-Meldung ansich. Deshalb die leeren try..except Blöcke rund um allen Quelltext, den ich für den Bösewicht halte. Trotzdem kommt die Meldung weiterhin.
"Mein Auto läuft einwandfrei, nur dieses Lämpchen, was aussieht wie eine Ölkanne, das stört. Wie kann man das abschalten.

Das ist die gleiche Logik!
Aber bitte mach ruhig.

Gruß
K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.757 Beiträge
 
Delphi 12 Athens
 
#5

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 10:27
Zeig doch mal die Deklarationen und Instanzierungen der TComponentList-Instanzen.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
berens

Registriert seit: 3. Sep 2004
441 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 10:40
In dem Execute vom Thread habe ich ja auch Protokoll-Funktion (wenn auch zunächst mal nur für Debugger), da dort das try..except nicht greift, habe ich die einzelnen Blöcke mit dem blanko try..except umgeben, um zu schauen, ob die Meldung nun verschindet, bzw. dort z.B. ein Sleep(0) gesetzt, um dort mit einem Haltepunkt anzuhalten. Irgendwie muss ich den Fehler ja eingrenzen.

Bei den TComponentListe hatte ich vor den Tests auch welche mit OwnsObjects=True dabei (wegen Garbage-Collection uns Leaks), das ändert allerdings nix an der Problematik.

Delphi-Quellcode:
  TLoadSaveThread = class(TThread)
  private
    FPanicLevel: integer;
    blStop, FblStop: Boolean;
    FslBenutzer: TStringList;

    FclAlleVorgaenge: TComponentList;
    FclBestellDetail: TComponentList;
    FclLebensStationen: TComponentList;

    FclProjekte: TComponentList;
    FclKontakte: TComponentList;
    FclWiederVorlage: TComponentList;
    FclTicket: TComponentList;
    FclMail: TComponentList;
    FclBestellung: TComponentList;
    FclArtikel: TComponentList;

    FclSave_Inbox: TComponentList;
    FclSave_Thread: TComponentList;
    FclSave_Outbox: TComponentList;

    FclDelete_Inbox: TComponentList;
    FclDelete_Thread: TComponentList;
    FclDelete_Outbox: TComponentList;

    FNewDataSet: TLoadSaveItem;
    FProgressText: string;
    procedure SyncSaveAndDelete;
    procedure SyncNewDataset;
    procedure SyncFinished;

    procedure SyncProjekte;
    procedure SyncKontakte;
    procedure SyncWiederVorlage;
    procedure SyncTickets;
    procedure SyncMails;
    procedure SyncBestellung;
    procedure SyncBestellDetails;
    procedure SyncVorgang;
    procedure SyncArtikel;


    procedure SyncStop;
    procedure SyncBenutzer;
    procedure SyncAblaufschritte;
    procedure SyncProgress;

  public
    clBestellDetail: TComponentList;
    clProjekte: TComponentList;
    clKontakte: TComponentList;
    clWiederVorlage: TComponentList;
    clTicket: TComponentList;
    clMail: TComponentList;
    clBestellung: TComponentList;
    clArtikel: TComponentList;

    clAlleVorgaenge: TComponentList;

    blTerminated: Boolean;
    slBenutzer: TStringList;
    ConnectionString: string;

    OnChange: TNotifyEvent;
    OnProgress: TNotifyEvent;
    ProgressText: string;
    PanicLevel: integer;

    ToDo: integer;

    procedure Execute; override;
    procedure Stop;

    procedure Delete(_LoadSaveItem: TLoadSaveItem);
    procedure Save(_LoadSaveItem: TLoadSaveItem);
    procedure SyncAlles;

    constructor Create(_Suspended: Boolean); reintroduce;
    destructor Destroy; override;
  end;


constructor TLoadSaveThread.Create(_Suspended: Boolean);
begin
  inherited;

  try
    OnChange := NIL;
    FblStop := False;

    FclSave_Inbox := TComponentList.Create(False);
    FclSave_Thread := TComponentList.Create(False);
    FclSave_Outbox := TComponentList.Create(False);

    FclDelete_Inbox := TComponentList.Create(False);
    FclDelete_Thread := TComponentList.Create(False);
    FclDelete_Outbox := TComponentList.Create(False);

    FclProjekte := TComponentList.Create(False);
    clProjekte := TComponentList.Create(False);

    FclKontakte := TComponentList.Create(False);
    clKontakte := TComponentList.Create(False);

    FclWiederVorlage := TComponentList.Create(False);
    clWiederVorlage := TComponentList.Create(False);

    FclTicket := TComponentList.Create(False);
    clTicket := TComponentList.Create(False);

    FclMail := TComponentList.Create(False);
    clMail := TComponentList.Create(False);

    FclBestellung := TComponentList.Create(False);
    clBestellung := TComponentList.Create(False);

    FclBestellDetail := TComponentList.Create(False);
    clBestellDetail := TComponentList.Create(False);

    FclArtikel := TComponentList.Create(False);
    clArtikel := TComponentList.Create(False);

    clAlleVorgaenge := TComponentList.Create(False);

    slBenutzer := TStringList.Create;
    ToDo := 0;
    blTerminated := False;
  except end;
end;



procedure TLoadSaveThread.Execute;
var
  db: TADOConnection;
  q: TADOQuery;
  i: integer;
  ...
begin
  q := NIL;
  db := NIL;
  blBenutzerGeladen := False;
  blVorlagenGeladen := False;
  blMailsGeladen := False;
  blArtikelGeladen := False;
  try
    FProgressText := 'Starten ...';
    Synchronize(SyncProgress);

    CoInitialize(NIL);
    FslBenutzer := TStringList.Create;
    FclAlleVorgaenge := TComponentList.Create(True);
    FclLebensStationen := TComponentList.Create(True);

    FProgressText := 'Verbinde Datenbank ...';
    Synchronize(SyncProgress);
    db := TADOConnection.Create(NIL);
    db.LoginPrompt := False;
    db.ConnectionString := Self.ConnectionString;
    db.Connected := True;

    FProgressText := 'Erstelle Abfrage ...';
    Synchronize(SyncProgress);
    q := TADOQuery.Create(NIL);
    q.Connection := db;
  Mit Zitat antworten Zitat
Blup

Registriert seit: 7. Aug 2008
Ort: Brandenburg
1.493 Beiträge
 
Delphi 12 Athens
 
#7

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 16:01
Exceptions in einem Thread zu debuggen ist zumindest problematisch.
Als Ursache kommt der Thread schon in Betracht, aber aufgetreten ist die Exception nach EurekaLog im Hauptthread.
Da nützen dies ganzen try..except-Blöcke wenig.

Aus den bisherigen Code-Schnipseln konnte ich keine Fehlerursache erkennen, aber einige Dinge sind schon aufgefallen:

Warum wird "OwnsObjects" bei allen Listen auf False gesetzt?
Irgendwer muss doch für das Freigeben der Objekte zuständig sein.

Es gibt verschiedene Variablen die sowohl private als auch public auftauchen:
Delphi-Quellcode:
private
  FPanicLevel: integer;
  FProgressText: string;
{...}
public
  ProgressText: string;
  PanicLevel: integer;
Da kann schnell mal was verwechselt werden.

"Execute" sollte eigentlich protected sein, nur der Thread selbst darf diese Methode aufrufen.

Delphi-Quellcode:
public
{...}
  blTerminated: Boolean;
  slBenutzer: TStringList;
  ConnectionString: string;
  OnChange: TNotifyEvent;
  OnProgress: TNotifyEvent;
{...}
Da es keine CriticalSection gibt, die den Zugriff auf öffentlich Properties schützt, darf der Zugriff darauf grundsätzlich nur im Hauptthread bzw. über Synchronize erfolgen.

Diese ganzen "public" Listen sollten eigentlich unabhängig vom Thread existieren.
Vieleicht eine unabhängige Klasse die alle Listen verwaltet und global bereitstellt.
Damit würde der Rest des Programms erst einmal von dieser Threadklasse entkoppelt.

Warum gibt es so viele verschiedene interne Listen?
Eigentlich wird doch immer nur eine Liste zur selben Zeit bearbeited.
Die Liste wird aus der Datenbank gelesen.
Dann im Synchronize mit einer öffentlichen Liste verarbeitet.
Welche das ist, ergibt sich aus dem Kontext.
Danach in die Datenbank geschrieben, der Inhalt gelöscht und steht wieder für die nächste Aufgabe zur Verfügung.

Man könnte überlegen für jede Aufgabe eine Klasse zu erstellen.
Die einzelnen Abgleiche unterscheiden sich im wesentlichen nur in:
- Name des Abgleichs
- SQL-Anweisung und Erstellen, Lesen und Speichern der Objekte
- der öffentlichen Liste für den Abgleich
Da lässt sich sicherlich eine Basisklasse bilden, die schon einen wesentlichen Teil einheitlich implementiert.
Alle Aufgaben in einer Liste die der Thread der Reihenfolge nach abarbeitet und schon sieht alles viel aufgeräumter aus.
Das lässt sich auch viel besser einzeln testen und erweitern.
  Mit Zitat antworten Zitat
berens

Registriert seit: 3. Sep 2004
441 Beiträge
 
Delphi 10.4 Sydney
 
#8

AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!

  Alt 20. Aug 2013, 16:45
Oben schonmal im Nebensatz: OwnsObject wurde zu Testzwecken überall mal auf "False" gesetzt, weil ich einfach schätzungsweise alles ausprobiert habe, den Fehler zu finden.

Alle doppelten Listen und Variablen verfolgen den Zweck, dass ich auf gar keinen Fall mit dem Mainthread auf Variablen/Objekte von diesem Thread zugreife.

Ich habe das Grundkonzept von Threads und "eiserne Regel", dass man vom Main-Thread nicht Objekte von (m)einem Thread zugreift (und andersrum) schon verstanden, aber ich glaube, ich setze das total falsch um.

Aus lauter Angst, versehentlich auf Variablen und Objekte zuzugreifen die gerade de andere Thread verwendet, habe ich immer alle Variablen und Objekte doppelt. Eine nur für den Main-Thread (z.B. clProjekte, public) und eine nur für den Thread (FclProjekte, private). Einzig in Synchronize(SyncBlabla) werden die Objekte von Thread nach Main kopiert und andersherum. Auch hier erstelle ich wieder ausschließlich neue Objekte, die mit Assign die Werte zugewiesen bekommen, vor lauter Sorge, wenn ich ein Objekt, das im Thread erstellt wurde aus einer TComponentList extrahierte (.Extract) und der ComponentList vom Main-Thread hinzufüge, dass dieses Objekt sich nicht 100% garantiert genau so verhält wie eines, das im Main-Thread erstellt wurde (vll. wegen Owner oder sonst irgendwelchen "Objektinternen"-Werten).

Ich werde mir CriticalSection mal genauer anschauen. Dies habe ich bisher noch nie verwendet, da ich davon ausgegangen bin, dass bei dem TThread-Objekt Synchronize() das empfohlende Pendant zu CriticalSection (Threads mit purer Api) ist.

Ich habe auch schon mehrere Thread-Tutorials durchgelesen. Haut mich, wenn ich einfach dabei gepennt habe, aber vielleicht sammeln wird hier gerade nochmal die wichtigsten Fakten, "DOs und DON'Ts".

Ich programmiere mit diesen Grundsätzen im Hinterkopf, bitte ergänzt oder korrigiert mich hier:
(Der Einfachheit halber nenne ich nachfolgend alle Objekte und Variablen etc. nur noch "Variablen".)

1) Mit dem Main-Thread darf man niemals auf Thread-Variablen zugreifen -weder lesend noch schreibend- die jemals im Thread.Execute verwendet werden

2) Im Thread.Execute darf niemals auf externe Variablen zugegriffen werden -weder lesend noch schreibend- (außerhalb des eigenen Objekts)

3) Im Thread.Execute dürfen keine VCL-Komponenten (speziell auch sichtbare Objekte) verwendet werden

4) Im Thread.Execute darf niemals auf Variablen des Thread-Objekts zugegriffen werden, die irgendwann mal von extern gesetzt oder gelesen werden sollen/können (z.B. public Variablen).

5) Im Thread verwendete Komponenten und Units müssen "Thread-Sicher" sein

6) Regel 1, 2, 4 dürfen evtl. mit CriticalSection umgangen werden???

7) Objekte, die im Thread.Execute erstellt wurden (mit oder ohne Owner) und mit Synchronize an das Hauptprogramm übergeben werden, lassen das Universum kollabieren. Dito in die andere Richtung: Im Hauptprogramm erstellte Objekte -speziell welche mit z.B. Owner=frmMain- dürfen auch mit Synchronize nicht an den Thread.Execute übergeben werden (also der direkte Pointer auf das Objekt), selbst wenn Synchronize die Referenz im MainThread auf dieses Objekt löscht), da der Thread -weshalb auch immer- z.B. auf den Owner zugreifen könnte und alles abstürzt.

8) ".Terminate" setzt einfach nur die Variable "Terminated" auf True. Ob der Thread wirklich beendet wurde, erfahre ich nur, wenn ich dafür eine eigene Variable setzte. (z.B. meine blTerminated)

9) Es ist für TThread.Execute kein Unterschied, ob ich eine TComponentList in .Create oder .Execute erstelle.


So, jetzt bekomme ich von Euch bestimmt gleich dieses o.g. Thread-Halb-Wissen um die Ohrengehauen. Lasset den Sturm beginnen.

Ne im Ernst, manchmal mach Delphi wirklich viel im Hintergrund und ich weiss nicht so recht, ob ich "das jetzt machen darf oder nicht". Mit dieser Arbeitsintensiven doppelt und dreifach Abspeicherei der Objekte will ich wirklich 100% sicher gehen, dass mein Programm nicht wegen dem Zugriff von Thread und Mainthread auf die selbe Variable abstürzt.
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:55 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