![]() |
Delphi-Version: 2007
Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Hallo mal wieder.
Ich bin mit meinem Latei mal wieder komplett am Ende, und selbst die SuFu oder Google liefern mir hier noch nicht mal mehr Ansätze. Ich habe hier (m)ein Programm, das aus einer Datenbank über einen Thread Werte lädt, diese mittels Synchronize() an eine Komponente aus dem Hauptformular übergibt und dann weiterverarbeitet wird. Das klappt alles 100% zuverlässig und störungsfrei. Nun ergibt sich aber das Problem, dass, sobald ich/der Benutzer etwas in ein TEdit oder TMemo eingibt, und (nach wilden rumtippseln auf der Tastatur) dann Leertaste drücke, eine Exception kommt. Alle TEdit/TMemo Objekte haben kein On-Ereignis hinterlegt. Alle Formulare haben KeyPreview auf "False". Es scheint so, als ob beim Drücken der Leertaste Delphi/Windows intern "Application.ProcessMessages" aufruft (siehe Stack weiter unten) und dann irgendwas im Hintergrund schafft. Ich vermute ja fast, dass Delphi alle Nachfahren von TObject, die momentan als Variable in meinem Programm/Speicher vorhanden sind benachrichtigt, dass der Hotkey "Leertaste" gedrückt wurde. Interessiert zwar keine einzige Komponente, aber egal. Während die Liste (wo nimmt er die her?) mit allen Komponenten durchgeht, wird wohl eine Komponente aus der Liste gelöscht, und es kommt zu der Exception. Meine Vermutung. Der Thread löscht keine Komponenten aus dem Hauptprogramm. Beim Debuggen wird mir beim Anklicken aller Threads nacheinander bei "Thread-Status" auch kein Thread angezeigt, in dem aktuell mein Quelltext läuft oder sonst irgendwie etwas gelöscht wird (es gibt ja immer mehrere "leere" Threads beim Debuggen wo nur "WaitForSingleObject" o.ä. als einziger Stackbefehl zu sehen ist). Der VCL/Main Thread ist idle und durchläuft zu diesem Zeitpunkt keine Prozeduren von mir. Ich verwende außer meinen Objekten und den Standard-Komponenten nur ein paar LMD-Panels etc., aber die werden nur bei Programmstart erzeugt. Keine weiteren Threads oder Fremdkomponenten. Wie bekomme ich denn raus, bei welcher TList der .Get-Befehl fehlgeschlagen ist? Was kann man machen? Kann ich der TApplication die Reaktionen auf Tastatur-Anschläge verbieten (MessageHandler überschreiben, ...)? Anbei das Log:
Code:
EurekaLog 7.0.6.0
Exception: ------------------------------------------------------------- 2.2 Address: 00436D20 2.5 Type : EListError 2.6 Message: Listenindex überschreitet das Maximum (21116). 2.7 ID : 60500000 2.11 Sent : 0 User: ----------------- 3.2 Name : name 3.3 Email: Steps to reproduce: ------------ 8.1 Text: Call Stack Information: ------------------------------------------------------------------------------------------------------------------------------------------------- |Methods |Details|Stack |Address |Module |Offset |Unit |Class |Procedure/Method |Line | ------------------------------------------------------------------------------------------------------------------------------------------------- |*Exception Thread: ID=4024; Parent=0; Priority=0 | |Class=; Name=MAIN | |DeadLock=0; Wait Chain= | |Comment= | |-----------------------------------------------------------------------------------------------------------------------------------------------| |7FFFFFFE|04 |00000000|00436D20|pCRM7.exe |00036D20|Classes |TList |Get |2992[2] | |00000020|04 |0018FBA8|00440ABB|pCRM7.exe |00040ABB|Classes |TComponent |GetComponent |10673[2] | |00000020|04 |0018FBB4|005034BF|pCRM7.exe |001034BF|Forms | |DispatchShortCut |5997[5] | |00000020|04 |0018FBD0|0050356D|pCRM7.exe |0010356D|Forms |TCustomForm |IsShortCut |6018[3] | |00000020|03 |0018FEB8|75BE6175|USER32.dll |00026175|USER32 | |SendMessageA | | |00000020|04 |0018FEDC|005067EB|pCRM7.exe |001067EB|Forms |TApplication |IsKeyMsg |8022[24] | |00000020|04 |0018FF08|00506A49|pCRM7.exe |00106A49|Forms |TApplication |ProcessMessage |8097[15] | |00000020|04 |0018FF20|00506AAE|pCRM7.exe |00106AAE|Forms |TApplication |HandleMessage |8124[1] | |00000020|04 |0018FF44|00506DA3|pCRM7.exe |00106DA3|Forms |TApplication |Run |8223[20] | |00000020|04 |0018FF74|006B05B5|pCRM7.exe |002B05B5|pCRM7 | |Initialization |39[8] | |00000020|03 |0018FFD8|77BD9F40|ntdll.dll |00039F40|ntdll | |(possible RtlInitializeExceptionChain+49)| | |-----------------------------------------------------------------------------------------------------------------------------------------------| | | |Running Thread: ID=5216; Parent=4024; Priority=0 | |Class=TLoadSaveThread; Name= (uLoadSave.TLoadSaveThread.Execute) | |DeadLock=0; Wait Chain=thread: [ 1460 / 5216 ] is blocked | |Comment= | |-----------------------------------------------------------------------------------------------------------------------------------------------| |7FFFFFFE|03 |00000000|77BBF8D1|ntdll.dll |0001F8D1|ntdll | |ZwWaitForSingleObject | | |00000020|03 |03F3F984|766F118F|kernel32.dll|0001118F|kernel32 | |WaitForSingleObjectEx | | |00000020|03 |03F3F99C|766F1143|kernel32.dll|00011143|kernel32 | |WaitForSingleObject | | |00000020|04 |03F3F9B0|0044028F|pCRM7.exe |0004028F|Classes |TThread |Synchronize |10168[34] | |00000020|04 |03F3FA00|00440336|pCRM7.exe |00040336|Classes |TThread |Synchronize |10196[4] | |00000020|04 |03F3FA08|006942DE|pCRM7.exe |002942DE|uLoadSave |TLoadSaveThread|Execute |1157[644] | |00000020|04 |03F3FE64|004DE045|pCRM7.exe |000DE045|EAppMultiThreaded| |ThreadExecuteSafeWrapper |64[11] | |00000020|04 |03F3FEA8|0043FE10|pCRM7.exe |0003FE10|Classes | |ThreadProc |9877[7] | |00000020|04 |03F3FED8|00405334|pCRM7.exe |00005334|System | |ThreadWrapper |12110[33] | |00000020|04 |03F3FEEC|004DE21E|pCRM7.exe |000DE21E|EAppMultiThreaded| |ThreadProcHandler |137[13] | |00000020|04 |03F3FF34|004C2607|pCRM7.exe |000C2607|EExceptionManager| |DefaultThreadHandleException |2850[3] | |00000020|04 |03F3FF78|0049D8C3|pCRM7.exe |0009D8C3|EThreadsManager | |ThreadWrapper |611[11] | |00000020|03 |03F3FF8C|766F33A8|kernel32.dll|000133A8|kernel32 | |BaseThreadInitThunk | | |7FFFFFFE|04 |00000000|0069135B|pCRM7.exe |0029135B|uLoadSave |TLoadSaveThread|Create |121[3] | ------------------------------------------------------------------------------------------------------------------------------------------------- Processes Information: ---------------------- Assembler Information: ----------------------------------------------------------- ; Classes.TList.Get (Line=2991 - Offset=9) ; ---------------------------------------- 00436D15 add eax, +$7C08733B ; ; Line=2991 - Offset=14 ; --------------------- 00436D1A jnp +$6C414415 ; ; Line=2992 - Offset=20 ; --------------------- 00436D20 add [ebx+$E8038BCE], cl ; <-- EXCEPTION ; ; Line=2992 - Offset=26 ; --------------------- 00436D26 push ds ; ; Line=2992 - Offset=27 ; --------------------- 00436D27 DB $FF, $FF // ; ; Line=2992 - Offset=29 ; --------------------- 00436D29 dec dword ptr [ebx+$48B0443] ; ; Line=2993 - Offset=35 ; --------------------- 00436D2F mov al, $5E ; ; Line=2994 - Offset=37 ; --------------------- 00436D31 pop ebx ; ; Line=2994 - Offset=38 ; --------------------- 00436D32 ret Registers: ----------------------------- EAX: 0018FAC4 EDI: 00000001 EBX: 00000000 ESI: 0EEDFADE ECX: 00000007 EBP: 0018FB14 EDX: 00000000 ESP: 0018FAC4 EIP: 7583C41F FLG: 00000212 EXP: 00436D20 STK: 0018FB9C Stack: Memory Dump: ------------------ --------------------------------------------------------------------------- 0018FBD8: 00000000 00436D20: 00 8B CE 8B 03 E8 1E FF FF FF 8B 43 04 8B 04 B0 ...........C.... 0018FBD4: 0018FBEC 00436D30: 5E 5B C3 90 8B 50 0C 83 FA 40 7E 0E 8B CA 85 C9 ^[...P...@~..... 0018FBD0: 00503572 00436D40: 79 03 83 C1 03 C1 F9 02 EB 11 83 FA 08 7E 07 B9 y............~.. 0018FBCC: 0018FBEC 00436D50: 10 00 00 00 EB 05 B9 04 00 00 00 03 CA 8B D1 E8 ................ 0018FBC8: 02742020 00436D60: 64 01 00 00 C3 8D 40 00 53 33 C9 EB 01 41 3B 48 d.....@.S3...A;H 0018FBC4: 00000018 00436D70: 08 7D 08 8B 58 04 3B 14 8B 75 F2 3B 48 08 75 03 .}..X.;..u.;H.u. 0018FBC0: 02742020 00436D80: 83 C9 FF 8B C1 5B C3 90 53 56 57 8B F9 8B F2 8B .....[..SVW..... 0018FBBC: 00503514 00436D90: D8 85 F6 7C 05 3B 73 08 7E 0F 8B 15 44 41 6C 00 ...|.;s.~...DAl. 0018FBB8: 00000000 00436DA0: 8B CE 8B 03 E8 9F FE FF FF 8B 43 08 3B 43 0C 75 ..........C.;C.u 0018FBB4: 005034C4 00436DB0: 06 8B C3 8B 10 FF 12 8B 43 08 3B F0 7D 1A 2B C6 ........C.;.}.+. 0018FBB0: 0ABF0580 00436DC0: 8B C8 03 C9 03 C9 8B 43 04 8D 54 B0 04 8B 43 04 .......C..T...C. 0018FBAC: 00000001 00436DD0: 8D 04 B0 E8 A4 C6 FC FF 8B 43 04 89 3C B0 FF 43 .........C..<..C 0018FBA8: 00440AC0 00436DE0: 08 85 FF 74 0B 33 C9 8B D7 8B C3 8B 18 FF 53 04 ...t.3........S. 0018FBA4: 02742020 00436DF0: 5F 5E 5B C3 8B 50 08 4A E8 0F FF FF FF C3 8B C0 _^[..P.J........ 0018FBA0: 0000527C 00436E00: 53 56 57 55 8B F1 8B FA 8B D8 3B F7 74 45 85 F6 SVWU......;.tE.. 0018FB9C: 00436D2A 00436E10: 7C 05 3B 73 08 7C 0F 8B 15 44 41 6C 00 8B CE 8B |.;s.|...DAl.... |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Was machst Du denn im Execute des Threads ausserhalb der Synchronize-Aufrufe?
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Beispiel: Laden der Daten aus Datenbank
Delphi-Quellcode:
Und dann mit Synchronize die in Objekte aus der THREAD-Componentliste (FclProjekte) in die VCL (Main-Thread)-Componentenliste (clProjekte) kopieren mittels assign.
TProjekt = class(TLoadSaveItem)
public txtProjektName: string; dtEndDatum: TDateTime; dtStartDatum: TDateTime; blAusblenden: Boolean; function Equals(_CompareTo: TProjekt): Boolean; procedure Assign(_Source: TPersistent); override; constructor Create(_Owner: TComponent); override; end; while (not q.Eof) and (not FblStop) and (ToDo = 0) do begin inc(intCounter); // OutputDebugString(pchar('LoadSave.Laden.Projekte.Counter: ' + inttostr(intCounter))); tmpProjekt := TProjekt.Create(NIL); with tmpProjekt do begin ID := q.FieldByName('ID').AsInteger; dtEndDatum := floor(q.FieldByName('dtEndDatum').AsDateTime); dtStartDatum := floor(q.FieldByName('dtStartDatum').AsDateTime); txtProjektName := q.FieldByName('txtProjektName').AsString; blAusblenden := q.FieldByName('blAusblenden').AsBoolean; LastUpdate := CurrentTick; end; // Genau dieses Projekt in der Liste der bereits geladenen Projekte suchen blGefunden := False; for i := 0 to FclProjekte.Count - 1 do begin if FclProjekte.Items[i].InheritsFrom(TProjekt) then begin ListenProjekt := TProjekt(FclProjekte.Items[i]); // Projekt gefunden if ListenProjekt.ID = tmpProjekt.ID then begin ListenProjekt.Assign(tmpProjekt); blGefunden := True; Break; end; end; end; if blGefunden then begin FreeAndNil(tmpProjekt); end else begin Synchronize(SyncNewDataset); FclProjekte.Add(tmpProjekt); end; q.Next; Synchronize(SyncSaveAndDelete); // damit auch beim Laden abgebrochen werden kann if (intCounter mod 10 = 0) then begin FProgressText := Format('%s von %s Projekten geladen', [IntToStr(intCounter), IntToStr(q.RecordCount)]); Synchronize(SyncProgress); end; end; q.Close; q.SQL.Clear; try i := 0; // Alle Einträge durchgehen while i < FclProjekte.Count do begin // Wenn Eintrag in der Liste nicht beim aktuellen Neu-Lade Zyklus mit dabei war tmpLS := TLoadSaveItem(FclProjekte.Items[i]); if tmpLS.LastUpdate <> CurrentTick then begin FclProjekte.Delete(i); end else begin inc(i); end; end; except end; Synchronize(SyncProjekte); FProgressText := Format('%s Projekte geladen', [IntToStr(intCounter)]); Synchronize(SyncProgress); end; Sollte ja nix ausmachen, dass die clProjekte ein Objekt des Threads ist? Schließlich wird ja mit .Execute NIE direkt darauf zugegriffen...
Delphi-Quellcode:
{$Region 'procedure TLoadSaveThread.SyncProjekte;'}
procedure TLoadSaveThread.SyncProjekte; var i, j: integer; blGeloescht, blGefunden: Boolean; tmpProjekt: TProjekt; begin SyncStop; try if FblStop then Exit; if FclProjekte.Count = 0 then Exit; while clProjekte.Count > 0 do begin tmpProjekt := TProjekt(clProjekte.Extract(clProjekte.Items[0])); FreeAndNil(tmpProjekt); end; if FblStop then Exit; except end; {$Region 'Alle Projekte aus dem Thread durchgehen'} try for i := 0 to FclProjekte.Count - 1 do begin if TLoadSaveItem(FclProjekte.Items[i]).InheritsFrom(TProjekt) then begin tmpProjekt := TProjekt.Create(NIL); tmpProjekt.Assign(TProjekt(FclProjekte.Items[i])); // Wurde das aktuelle Projekt zwischenzeitlich gelöscht? blGeloescht := False; for j := 0 to FclDelete_Outbox.Count - 1 do begin if TLoadSaveItem(FclDelete_Outbox.Items[j]).InheritsFrom(TProjekt) then begin if TLoadSaveItem(FclDelete_Outbox.Items[j]).ID = tmpProjekt.ID then begin blGeloescht := True; Break; end; end; end; // Falls gelöscht, nicht in die VCL-Liste übernehmen if blGeloescht then begin FreeAndNil(tmpProjekt); end else begin // Falls nicht gelöscht, nachsehen // ob dieser Eintrag zwischenzeitlich bearbeitet und gespeichert wurde. // Dann diesen Wert bevorzugen, denn das Abspeichern in der Datenbank ist noch nicht beendet. j := 0; while j < FclSave_Outbox.Count do begin if TLoadSaveItem(FclSave_Outbox.Items[j]).InheritsFrom(TProjekt) then begin if TLoadSaveItem(FclSave_Outbox.Items[j]).ID = tmpProjekt.ID then begin // falls das Objekt aus dem Datenbankthread identisch ist mit dem, was gespeichert werden soll, // war das speichern und anschließende Neu-Laden wohl erfolgreich. if TProjekt(FclSave_Outbox.Items[j]).Equals(tmpProjekt) then begin FclSave_Outbox.Delete(j); end else begin // Abspeichern noch nicht beendet, deswegen Daten aus der Datenbank mit denen aus dem Zwischenspeicher ersetzen tmpProjekt.Assign(FclSave_Outbox.Items[j]); inc(j); end; end else inc(j); end else inc(j); end; clProjekte.Add(tmpProjekt); end; end; // inheritsfrom TProjekt end; // Alle Projekte except end; {$EndRegion 'Alle Projekte aus dem Thread durchgehen'} {$Region '"Zu speichernde" -also neue- Projekte laden'} try i := 0; while i < FclSave_Outbox.Count do begin // Ist das "zu speichernde" Projekt (in Save_Outbox) denn "schon" in clProjekte enthalten? blGefunden := False; for j := 0 to clProjekte.Count - 1 do begin if TLoadSaveItem(clProjekte.Items[j]).ClassType = TLoadSaveItem(FclSave_Outbox.Items[i]).ClassType then begin if TLoadSaveItem(clProjekte.Items[j]).ID = TLoadSaveItem(FclSave_Outbox.Items[i]).ID then begin blGefunden := True; end; end; end; // Falls noch nicht in clProjekte vorhanden if not blGefunden then begin if FclSave_Outbox.Items[i].InheritsFrom(TProjekt) then begin tmpProjekt := TProjekt.Create(NIL); tmpProjekt.Assign(TProjekt(FclSave_Outbox.Items[i])); clProjekte.Add(tmpProjekt); end; end; inc(i); end; except end; {$EndRegion '"Zu speichernde" -also neue- Projekte laden'} {$Region 'Wurde das zu löschende Projekt nicht mehr erneut aus der Datenbank geladen?'} try i := 0; // Liste der zu löschenden Objekte durchgehen while i < FclDelete_Outbox.Count do begin blGefunden := False; // vergleichen mit dem, was eben geladen wurde for j := 0 to FclProjekte.Count - 1 do begin if TLoadSaveItem(FclProjekte.Items[j]).ClassType = TLoadSaveItem(FclDelete_Outbox.Items[i]).ClassType then begin // Falls das zu löschende Objekt immer noch in der Liste gefunden wird, abbrechen if TLoadSaveItem(FclProjekte.Items[j]).ID = TLoadSaveItem(FclDelete_Outbox.Items[i]).ID then begin blGefunden := True; Break; end; end; end; // Wenn das zu löschende Objekt nicht mehr aus der Datenbank geladen wird, wurde es erfolgreich gelöscht if blGefunden then begin inc(i); end else begin // Nachverfolgung erfolgreich, da der zu löschende Datensatz nicht erneut aus der Datenbank geladen wurde! FclDelete_Outbox.Delete(i); end; end; except end; {$EndRegion 'Wurde das zu löschende Projekt nicht mehr erneut aus der Datenbank geladen?'} end; {$EndRegion} |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Und Du bist sicher dass Deine Datenbank-Connection bzw. der dort verwendete Treiber Threadsafe ist? Da dann der Fehler nämlich in einer externen dll ausgelöst wird, ergibt sich auch kein ordentlicher Callstack.
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Über TAdoQuery und TAdoConnection via Ole/Jet auf eine .mdb Datei. Nicht das was eine professionelle Lösung ist, aber das, womit ich arbeiten muss. Außer in dem Thread selbst verwende ich die Ado-Komponenten nirgendwo, falls das irgendwas zur Sache tut...
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
![]() Testen könntest du das außer mit Pings, indem du mal probierst, mit eingeschaltetem Cache zu arbeiten: Alle Daten werden erstmal lokal auf dem Rechner gespeichert, danach wird die Verbindung wieder getrennt. Und immer schön Try-Except-Blöcke verwenden, das vermindert die Absturzhäufigkeit im Fehlerfalle :stupid: |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Ich werfe auch noch kurz 3 Aspekte in die Runde...
Bei ADO/dbGo muss jeder Thread, der auf die DB zugreift eine eigene TADOConnection haben. Sonst funktioniert das Connection Pooling nicht. Jeder Thread der ADO nutzt muss sein eigenes CoInitialize aufrufen. Je nach Anwendung würde ich abwägen, ob ich COM ggf. als MULTITHREADED initialisiere. Das läuft oft runder, wenn dein Programm nicht auf OLE, Clipboard oder Shell Dialoge zugreift. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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 (
Delphi-Quellcode:
)
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:
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.if blGefunden then begin FreeAndNil(tmpProjekt); end else begin Synchronize(SyncNewDataset); // <--- was wird denn hier synchronisiert? FclProjekte.Add(tmpProjekt); end; 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. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Insbesondere der Zugriff aus einem Thread auf eine VCL-Komponente ist ein sicherer Absturzkandidat. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Das ist die gleiche Logik! Aber bitte mach ruhig. Gruß K-H |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zeig doch mal die Deklarationen und Instanzierungen der TComponentList-Instanzen.
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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; |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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:
Da kann schnell mal was verwechselt werden.
private
FPanicLevel: integer; FProgressText: string; {...} public ProgressText: string; PanicLevel: integer; "Execute" sollte eigentlich protected sein, nur der Thread selbst darf diese Methode aufrufen.
Delphi-Quellcode:
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.
public
{...} blTerminated: Boolean; slBenutzer: TStringList; ConnectionString: string; OnChange: TNotifyEvent; OnProgress: TNotifyEvent; {...} 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. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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. :thumb: 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. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Ich habe dieses Thema seit Bestehen auf meiner "Muss ich mir näher anschauen und nicht nur überfliegen"-Liste, bislang ist das nicht geschehen. Deswegen nur eine Reaktion direkt auf den den letzten Beitrag, ohne die ganze Geschichte zu kennen:
Zitat:
Ich finde du machst es dir unnötig kompliziert. Es gibt zwei eiserne und einfache Regeln:
Das war's. Zur weiteren Erläuterung: 1) Du bist standardmäßig immer im Hauptthread. Ausnahmen sind die Execute-Methode einer TThread-Klasse und beispielsweise die entsprechenden "Behandle Netzwerkpaket"-Handler von Serverkomponenten wie Indy. 2) Schützen bedeutet: Sicherstellen, dass niemand die Daten lesen (oder ändern) kann, während jemand anderes gerade am ändern ist. Für reine Variablen bieten sich
Delphi-Quellcode:
(was für ein Klassenname) perfekt an. Erstelle dir einen (oder mehrere), und bevor du eine Variable ändern willst, sage "mutex.beginRead();". Wenn du fertig gelesen hast, sag "mutex.endRead();". Ebenso mit dem Schreiben.
TMultiReadExclusiveWriteSynchronizer
Parallel gibt es kritische Abschnitte, die Delphi Klasse
Delphi-Quellcode:
kapselt hier eigentlich schon alles was man braucht.
TCriticalSection
Ich würde vielleicht nicht einmal ein konkretes technisches Tutorial, wie man Threads in Programmiersprache XY hinbekommt, sondern etwas theoretische Grundlagen lesen. Ich habe gerade leider keinen Vorschlag parat, aber vielleicht jemand anderes. Es tut niemandem weh, wenn du bsp. im VCL-Thread "Objekte benutzt", die in einem anderen erstellt wurden. Niemandem tut es weh, wenn mehrere Threads gleichzeitig die gleiche Methode auf einem einzigen Objekt aufrufen, und dieses Objekt in einem Thread erstellt wurde, der schon längst tot ist. Es sind eigentlich nur diese beiden Regeln. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Wenn ich mal auf den Call-Stack zurück kommen darf: die Fehlermeldung impliziert, daß auf die 21116. Komponente in einem Form (vermutlich dem MainForm) zugegriffen wird. Dieser Zugriff erfolgt in DispatchShortCut was allerdings in einer Schleife alle Komponenten iteriert. Sollte also nicht innerhalb dieser Schleife die Liste (aus einem anderen Thread) verändert werden, sollt dieser Fehler gar nicht vorkommen.
Bedenklich finde ich allerdings diese Zahl 21116, die ja nur über die Schleifenvariable oder einen kaputten Stack entstehen kann. Daher erst mal die Frage: Kann es sein, daß eines deiner Forms eine derart hohe Zahl von Komponenten hat? In dem Fall würde ich blind behaupten, du machst da was falsch. Deine Bemerkung "diese mittels Synchronize() an eine Komponente aus dem Hauptformular übergibt" klingt dabei aber verdächtig. Mir ist auch aufgefallen, daß du das Execute durch unerwartet viele Synchronize-Aufrufe unterbrichst - insbesondere in der while-Schleife. Damit konterkarierst du aber eigentlich den Sinn eines Threads. Das einzige, was du mit dem Thread erreichst, ist, daß deine Anwendung beim
Delphi-Quellcode:
nicht einfriert, wenn Daten nachgeladen werden müssen. Du könntest ja mal versuchen, die benötigten Daten ohne Verwendung eines Threads mit Hilfe eines DataSetProviders in ein ClientDataSet zu laden. Wie lange dauert das?
q.Next;
Auch der Kommentar "damit auch beim Laden abgebrochen werden kann" hinter dem
Delphi-Quellcode:
zeugt von einem suboptimalen Ansatz: man kann einen Thread auch (und sogar wesentlich effizienter) ohne Aufruf eines Synchronize abbrechen.
Synchronize(SyncSaveAndDelete);
Es ist schwer in deinem Code den wirklichen Fehler zu finden, ohne daß du größere Teile des Projekts zur Verfügung stellst. Aber selbst wenn du das tust, muss sich auch noch jemand finden, der dann noch in die Fehlersuche einsteigt. Das könnte den Rahmen einer kostenlosen Hilfe unter Forenteilnehmern eventuell sprengen. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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:
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 :( ).
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; Eine kleine Änderung in:
Delphi-Quellcode:
Und ... oh Wunder der Technik: aktuell läuft es. :thumb:
tmpBestellung := TBestellung.Create(NIL);
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. |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Zitat:
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Zitat:
Wenn du wirklich in einem Thread direkt etwas an der VCL-Oberfläche ändern musst, dann ist dazu
Delphi-Quellcode:
bzw.
TThread.Synchronize(..)
Delphi-Quellcode:
da.
TThread.Queue(..)
Zitat:
Zitat:
Außerdem wiederhole ich gerne noch einmal mein "Du machst es dir unnötig kompliziert": Dein TThread-Objekt hat ganz komfortabel schon Eigenschaften wie
Delphi-Quellcode:
oder
Terminated
Delphi-Quellcode:
. Die kannst du einfach lesen und brauchst dir keine Sorgen machen. Und nichts eigenes erfinden.
Finished
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
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:
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.
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 } Weiterhin Danke an Alle! :) |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Falls es die
Delphi-Quellcode:
-Property noch nicht gibt, dann doch wenigstens das
Finished
Delphi-Quellcode:
-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.
onTerminate
Darin kannst du ja dann auch einfach eine
Delphi-Quellcode:
setzen.
meineForm.derThreadIstFertig := True
|
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Willst du im Hauptthread auf das Ende von MyThread warten, hilft MyThread.WaitFor (gegebenfalls mit einem Timeout). |
AW: Exception ohne wirklichen Auslöser treibt mich in den Wahnsinn!
Zitat:
Das steht weiter oben im Beispiel der OH:
Delphi-Quellcode:
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).
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; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:48 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