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
berens

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

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

  Alt 20. Aug 2013, 19:12
Genau das ist ja das, was mich die ganze Zeit stutzig gemacht hat: Eine Prozedur im Hauptthread löst eine Exception mit Objekten aus dem Thread aus. Vollkommen unsinnig... Wäre da nicht... Das gute alte Copy&Paste, wenn man einfach 1:1 Sachen aus seiner Technik-Demo in das fertige Programm übernimmt:

Beim gezielten Suchen nach dem Hauptformular habe ich festgestellt, dass nicht nur in meiner Thread-Unit, sonder direkt in der Thread.Execute ein verweis auf das VCL-Hauptformular frmMain ist. Und das natürlich noch an bester Stelle:

Delphi-Quellcode:
            tmpBestellung := TBestellung.Create(frmMain);
            with tmpBestellung do begin
              ID := q.FieldByName('ID').AsInteger;
              refProjekt := q.FieldByName('refProjekt').AsInteger;
              dtBestellung := q.FieldByName('dtBestellung').AsDateTime;
              dtAenderung := q.FieldByName('dtAenderung').AsDateTime;

              LastUpdate := CurrentTick;
            end;
Das gehört in einem Thread natürlich auf keinen Fall so (wie gesagt: ich habe es damals halt erst alles naträglich in einen Thread umgeschrieben ).

Eine kleine Änderung in:
tmpBestellung := TBestellung.Create(NIL); Und ... oh Wunder der Technik: aktuell läuft es.

Was mich nun wieder speziell an meine "Regeln" oben erinnert: Was zur Hölle schafft Delphi/Windows mit dem "Owner" so krasses? Ich denke mal, dadurch, dass die (Thread-)Objekte in jedem Thread-Durchlauf neu erzeugt und dann wieder komplett gelöscht werden wird Aufgrund diesen Fehlers jedes Mal eine Nachricht an frmMain gesendet wie "Du besitzt nun ein neues Objekt, für dessen .Free du zuständig bist: tmpBestellung". Wobei dieses Objekt dann gleich wieder gelöscht wird, und das Senden dieser Nachricht an frmMain wahrscheinlich gleichermaßen ein Schwerverbrechen ist, wie direkt frmMain.Caption := 'Hallo' im Thread.Execute zu schreiben.

Uwe: Erklär mir doch bitte noch in 1-2 Sätzen, wie ich den Thread ohne Aufruf eines Synchronize abbrechen kann. Suspend pausiert ja nur, und lässt nicht wirklich ein sauberes Ende zu (Komponenten freigeben, AdoVerbindung ordentlich beenden etc. Soll ich z.B. eine Prozedur schreiben, in der ich über CriticalSection einfach z.B. eine Boolean-Variable setzt, und diese im Thread-Verlauf ganz normal auslese ("if not blStop then begin...")?

"Der schöne Günther": Die Felder meiner von TThread abgeleiteten Klasse.
Wenn ich Dich richtig verstehe, dürfen im Prinzip 1000 verschiedene Threads problemlos gleichzeitig tmpBestellung.ID auslesen, wenn diese irgendwie an das Objekt kommen. Was passiert, wenn dieses Objekt eine public-Variable/Objekt von frmMain ist, z.B. frmMain.AktuelleBestellung (nur als Beispiel!)?

Blup: "Execute" sollte eigentlich protected sein. Korrekt. Korrigiert.

Ich hoffe, dass das Projekt jetzt dauerhaft ordentlich läuft. Ich fahre jetzt alles wieder auf den alten Stand (auch OwnsObject und Freigabe der Objekte etc.) Somit war scheinbar alles mal wieder ein Fehler des Schlampigkeit. Trotzdem ein Rüffel an Delphi, irgendwo könnte ja auch stehen, dass z.B. der Fehler beim Zugriff auf die vom frmMain "ge-owned-en" Objekte (wie ist das Wort?), also die Objekte, deren Owner frmMain ist.

Tut mir wie immer Leid, Eure Arbeits- oder Freizeit stark in Anspruch genommen zu haben.
Ich danke vielmals für die geduldigen Antworten!

Anbei nochmal "meine" überarbeiteten Thread-Regeln, mit der Einladung an alle, die sie lesen, mich zu verbessern:
Speziell zu 8: Was passiert, wenn ich ein Object mit Owner = frmMain an den Thread übergebe, und dieser es löschen soll? Muss ja dann zwangläufig mit Synchronize erfolgen, auch wenn ich mit dem Hauptprogramm selber nie wieder schreibend auf das Objekt zugreifen will. Oder?

Darf ich in CriticalSection auf VCL-Objekte zugreifen? Ich muss mich da mal einlesen.

WICHTIGE Frage: Darf ich denn Variable, die garantiert nur vom Thread geschrieben werden (z.B. blTerminated) jederzeit ohne CriticalSelection schreiben, oder muss ich, weil ich mit frmMain im 10ms Takt "if LoadSave.blTerminated" beispielhaft überprüfe, ob der Thread fertig beendet ist unbedingt die CriticalSelection verwenden? frmMain ließt ja nur!?

Code:
1) Mit dem Main-Thread darf man niemals [I]schreibend [/I]auf Thread-Variablen oder Objekte zugreifen, die jemals im Thread.Execute verwendet werden

2) Im Thread.Execute darf niemals [I]schreibend [/I] auf externe Variablen oder Objekte zugegriffen werden

3) Im Thread.Execute dürfen keine VCL-Komponenten verwendet werden. Wenn irgendwo eine (speziell "Visuelle" - also sichtbare) Komponente referenziert wird (z.B. das Hauptformular), ist das zu 100% ein Fehler! Nicht-VCL-Objekte die z.B. unter "public" bei einer VCL-Komponente stehen, dürfen angesprochen 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 mit CriticalSection umgangen werden.

7) Objekte, die im Thread.Execute erstellt wurden (ohne Owner!) dürfen an das Hauptprogramm übergeben werden, sofern Thread.Execute nie wieder schreibend darauf zugreift.

8) In die andere Richtung: Im Hauptprogramm erstellte Objekte -speziell welche mit z.B. Owner=frmMain- dürfen den Thread.Execute übergeben werden, sofern nur gelesen wird.

9) ".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) Diese Variable kann nur CriticalSelection oder Synchronize im Thread geschrieben werden, und kann/darf problemlos jederzeit vom Hauptprogramm ausgelesen werden. OnTerminate oder "Finished" ist eine bessere Lösung.

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

Geändert von berens (20. Aug 2013 um 20:40 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Olli73
Olli73

Registriert seit: 25. Apr 2008
Ort: Neunkirchen
782 Beiträge
 
#2

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

  Alt 20. Aug 2013, 19:48
Was mich nun wieder speziell an meine "Regeln" oben erinnert: Was zur Hölle schafft Delphi/Windows mit dem "Owner" so krasses? Ich denke mal, dadurch, dass die (Thread-)Objekte in jedem Thread-Durchlauf neu erzeugt und dann wieder komplett gelöscht werden wird Aufgrund diesen Fehlers jedes Mal eine Nachricht an frmMain gesendet wie "Du besitzt nun ein neues Objekt, für dessen .Free du zuständig bist: tmpBestellung". Wobei dieses Objekt dann gleich wieder gelöscht wird, und das Senden dieser Nachricht an frmMain wahrscheinlich gleichermaßen ein Schwerverbrechen ist, wie direkt frmMain.Caption := 'Hallo' im Thread.Execute zu schreiben.
Das (alleine) hat hier nicht das Problem ausgelöst, sondern das was Uwe Raabe in #17 geschrieben hat. DispatchShortCut läuft alle Komponenten des Formulars durch, also auch die, denen du versehentlich im Thread frmMain als owner zugewiesen hast. -> Aber huch, wo ist denn die Komponente hin, mitten in meiner Schleife hat doch der böse böse Thread...


WICHTIGE Frage: Darf ich denn Variable, die garantiert nur vom Thread geschrieben werden (z.B. blTerminated) jederzeit ohne CriticalSelection schreiben, oder muss ich, weil ich mit frmMain im 10ms Takt "if LoadSave.blTerminated" beispielhaft überprüfe, ob der Thread fertig beendet ist unbedingt die CriticalSelection verwenden? frmMain ließt ja nur!?
Bei boolean gibt es (wahrscheinlich) keine Probleme. Aber Strings z.B.(!) werden bei Bedarf umkopiert, wenn das dann mitten im Lesevorgang passiert ist das schlecht. Aber auch ohne Umkopieren, du änderst z.B. gerade im Thread den String 'abcd' auf 'wxyz', dann könnte der Hauptthrad z.B. 'wxcd', und damit rechnest du wahrscheinlich nicht.
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.199 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

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

  Alt 20. Aug 2013, 19:58
Zitat:
Wenn ich Dich richtig verstehe, dürfen im Prinzip 1000 verschiedene Threads problemlos gleichzeitig tmpBestellung.ID auslesen, wenn diese irgendwie an das Objekt kommen. Was passiert, wenn dieses Objekt eine public-Variable/Objekt von frmMain ist, z.B. frmMain.AktuelleBestellung (nur als Beispiel!)?
Nur weil es ein Feld einer Form ist, heißt es nicht, dass man es nicht anfassen darf. Mit "VCL-Dinge" meinte ich bsp. Label aktualisieren, Komponenten verstecken. meineForm.verweisAufIrgendetwasAnderes kann man problemlos aus anderen Threads anfassen. Nur nicht vergessen: Wenn sich mehrere Threads für irgenetwas interessieren könnten, muss das entsprechend geschützt werden.

Zitat:
Darf ich in CriticalSection auf VCL-Objekte zugreifen? Ich muss mich da mal einlesen.
Ein kritischer Abschnitt ist keine Magie. Synchronisationshilfen wie KA oder anderes bringt nur etwas, wenn sich alle Teilnehmer auch daran halten. Und die Delphi VCL-Logik wird sich sicher nicht an deinen selbst eingeführten KA halten.

Wenn du wirklich in einem Thread direkt etwas an der VCL-Oberfläche ändern musst, dann ist dazu TThread.Synchronize(..) bzw. TThread.Queue(..) da.

Zitat:
Darf ich denn Variable, die garantiert nur vom Thread geschrieben werden (z.B. blTerminated) jederzeit ohne CriticalSelection schreiben, oder muss ich, weil ich mit frmMain im 10ms Takt "if LoadSave.blTerminated" beispielhaft überprüfe, ob der Thread fertig beendet ist unbedingt die CriticalSelection verwenden? frmMain ließt ja nur!?
Könnte man glauben, das reicht aber leider nicht. In der Praxis ist die Wahrscheinlichkeit, dass es da knallt zwar extrem gering, aber wie gesagt (es sind eigentlich wirklich nur die zwei Regeln): Sobald sich mehr als ein Thread dafür interessiert: Absichern.
Bei boolean gibt es (wahrscheinlich) keine Probleme. [...]
Auf SO gab es vor ein paar Tagen genau das Problem, dass eine Integer-Variable in einem Thread hochgezählt wurde und der Hauptthread griff nur lesend zu. Hat auf Dauer auch nicht geklappt. Ich würde nie davon ausgehen, dass da irgendetwas atomar abläuft. Auch bei Boolean nicht.

Außerdem wiederhole ich gerne noch einmal mein "Du machst es dir unnötig kompliziert": Dein TThread-Objekt hat ganz komfortabel schon Eigenschaften wie Terminated oder Finished . Die kannst du einfach lesen und brauchst dir keine Sorgen machen. Und nichts eigenes erfinden.
  Mit Zitat antworten Zitat
berens

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

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

  Alt 20. Aug 2013, 20:14
Günther: Bin mir nicht sicher, aber ich glaube in Delphi 2007 gibt es TThread.Finished nicht.

TThread.Terminate setzt ja nur die Boolean-Variable des Threads auf Terminated, ich kann ja (ohne das Finished von oben) nicht so einfach ohne eigene Variable feststelle, ob .Execute nun ordnungsgemäß beendet wurde?

Klar, es gibt Event.WaitFor. Damit habe ich noch nie gearbeitet, und es scheint doch unverhältnismäßig komplizierter zu sein, als eine einfache Booleanvariable im Thread.

Zumindest das Beispiel aus der OH ist nicht wirklich selbsterklärend:
Delphi-Quellcode:
Event1.ResetEvent(); { Ereignis vor der Ausführung der Threads zurücksetzen }
for i := 1 to Counter do
TaskThread.Create(False); { Aufgaben-Threads erzeugen und ausführen }
if Event1.WaitFor(20000) <> wrSignaled then
raise Exception;
{ nun den Haupt-Threads fortsetzen Alle Aufgaben-Threads sind beendet }
Woher weiß z.B. das Event, dass es auf meinen Thread warten soll? Hier in dem Beispiel werden mehrere Threads erzeugt. Woher weiß das Event, dass alle fertig sind? Wird wrSignaled immer von Thread nach der Beendigung von Terminate gesetzt? Fragen über Fragen.

Weiterhin Danke an Alle!
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.199 Beiträge
 
Delphi 10 Seattle Enterprise
 
#5

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

  Alt 20. Aug 2013, 20:30
Falls es die Finished -Property noch nicht gibt, dann doch wenigstens das onTerminate -Ereignis? Das wird ausgeführt, wenn der Thread seine Execute-Methode verlassen hat. Als Bonus wird die Methode bereits im Kontext des Hauptthreads ausgeführt, du kannst also schon gefahrlos an VCL-Komponenten herumwerkeln.

Darin kannst du ja dann auch einfach eine meineForm.derThreadIstFertig := True setzen.
  Mit Zitat antworten Zitat
Benutzerbild von Olli73
Olli73

Registriert seit: 25. Apr 2008
Ort: Neunkirchen
782 Beiträge
 
#6

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

  Alt 20. Aug 2013, 21:26
Zumindest das Beispiel aus der OH ist nicht wirklich selbsterklärend:
Delphi-Quellcode:
Event1.ResetEvent(); { Ereignis vor der Ausführung der Threads zurücksetzen }
for i := 1 to Counter do
TaskThread.Create(False); { Aufgaben-Threads erzeugen und ausführen }
if Event1.WaitFor(20000) <> wrSignaled then
raise Exception;
{ nun den Haupt-Threads fortsetzen Alle Aufgaben-Threads sind beendet }
Hier in dem Beispiel werden mehrere Threads erzeugt. Woher weiß das Event, dass alle fertig sind?
Der Letzte macht das Licht aus
Das steht weiter oben im Beispiel der OH:

Delphi-Quellcode:
 procedure TDataModule.TaskTerminateThread(Sender: TObject);
 begin
   ...
   CounterGuard.Acquire; { Zähler mit einer Sperre belegen }
   Dec(Counter); { Wert der globalen Zählervariable verringern }
   if Counter = 0 then
     Event1.SetEvent; { Signalisieren, ob es der letzte Thread ist }
   Counter.Release; {Sperre vom Zähler entfernen}
   ...
 end;
Der Counter wird im OnTerminate eines jeden Threads heruntergezählt (mit critical section versteht sich!). Wenn 0 erreicht ist, weiß der Thread, dass er der letzte ist und macht das Lich aus (SetEvent).
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

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

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

  Alt 20. Aug 2013, 21:21
Uwe: Erklär mir doch bitte noch in 1-2 Sätzen, wie ich den Thread ohne Aufruf eines Synchronize abbrechen kann.
Innherhalb des Execute fragst du regelmäßig Terminated ab. Wenn du von außerhalb den Thread beenden willst, rufst du MyThread.Terminate (oder wie die Variable auch heißt, in der du dir Thread-Instanz ablegst) auf. Das ist der empfohlene Weg.

Willst du im Hauptthread auf das Ende von MyThread warten, hilft MyThread.WaitFor (gegebenfalls mit einem Timeout).
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  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 05:01 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