VCL und Threadsicherheit
-
Heißt das etwa auch, dass ein Aufruf von 'Synchronize()' nur aus der Execute()-Methode erfolgen sollte und andere Methoden, welche über die Execute()-Methode aufgerufen werden, sollten dann darauf verzichten?
Nein!
Grundsätzlich gilt:
Solange sich der Thread-Process innerhalb von Execute() oder einer in Execute() aufgerufenen Methode befindet, MUSS Synchronize() verwendet werden.
Befindet sich der Thread-Process außerhalb von Execute() (Konstruktor, Destruktor, OnTerminate()), dann darf Synchronize() NICHT verwendet werden.In deinem Fall:
Wenn du test() in Execute() aufrufst , dann mußt du auch in test() Synchronize() verwenden.
Falls du test() aber im Konstruktor, Destruktor oder in OnTerminate() aufrufst, darfst du in test() kein Synchronice() verwenden.Der Thread ist ein eigenständiger Process, aber nur solange er sich innerhalb von Execute() oder einer in Execute() aufgerufenen Methode befindet.
Vor dem Einsprung in Execute() und nach dem Verlassen von Execute() ist der Thread ein Teil des Haupt-Processes (Main).Synchronice() dient dazu, Thread- und Haupt-Process miteinander zu synchronisieren. Beide Processe laufen parallel. Wenn der Thread nun eine Funktion des Haupt-Processes aufruft, dann muss der Haupt-Process zuerst seine aktuelle Arbeit stoppen, Register, Stack etc. sichern, erst dann kann er die vom Thread aufgerufene Methode ausführen.
Falls Synchronize() außerhalb des Thread-Processes verwendet wird, versucht der Haupt-Process sich mit sich selbst zu synchronisieren. Das kann hundert mal gut gehen (je nachdem, was der Haupt-Process gerade macht), aber wenn es schiefgeht, dann so, dass nichts mehr geht. Und diesen Fehler, der nur sporadisch auftritt, kann man fast nicht lokalisieren.
Ich habe die Erfahrung gemacht, dass so ein Synchronisationsfehler nur auftritt, wenn das Programm eigenständig läuft. Innerhalb der IDE mit Debugger usw. läßt sich das nicht nachvollziehen, da funktioniert es immer.
-
Danke für diese ausführliche Erklärung. Damit hast du mir schonmal sehr weitergeholfen.
Denn so ganz sicher war ich mir bei den Aufrufen vom 'Synchronize()' auch nicht.Ich habe noch ein weiteres "Problem" bei dem ich mir nicht sicher bin:
1.) Ich rufe aus der 'Execute()'-Methode eine Funktion auf 'Synchronize(Zeichnen)';
2.) Die Funktion erstellt dann ein TPanel://--------------------------------------------------------------------------- void __fastcall TTrdExecute::Zeichnen(void) { TPanel *MyPanel; MyPanel = new TPanel(MainForm); MyPanel->Parent = MainForm; MyPanel->Name = "MyPanel"; MyPanel->Left = 50; MyPanel->Top = 125; MyPanel->Width = 65; MyPanel->Height = 65; MyPanel->Caption = "test"; MyPanel->BevelInner = bvNone; MyPanel->BevelOuter = bvNone; MyPanel->ParentFont = true; MyPanel->Font->Size = 15; MyPanel->ParentColor = true; // Funktionsaufruf für 'OnClick()'-Methode zuweisen MyPanel->OnClick = MyPanelClick; // Zeigerzuweisung löschen MyPanel = NULL; } //---------------------------------------------------------------------------
3.) Die 'OnClick()'-Methode wird ja nun aufgerufen, wenn ich den Panel anklicke...
Frage: Brauche ich dann innerhalb dieser 'OnClick()'-Methode das 'Synchronize()' für Funktionsaufrufe?//--------------------------------------------------------------------------- void __fastcall TTrdExecute::MyPanelClick(TObject *Sender) { // Zeiger auf den Sender zu TPanel casten TPanel *MyPanel = dynamic_cast<TPanel*>(Sender); if (MyPanel) { // Panel löschen delete MyPanel; } // Funktionsaufruf MIT oder OHNE 'Synchronize()' ??? Test(); } //---------------------------------------------------------------------------
Die nächste Frage wäre außerdem ob das mit dem TPanel und dem Speicher klar geht.
Muss ich mich noch um den Speicher kümmern, oder reicht das 'casten' und das 'delete' aus?Edit:
Casten vom Panel klappt, aber beim 'delete' gibts ne Zugriffsverletzung.
Wie müsste ich dass denn jetzt lösen, wenn ich das Panel im OnClick löschen will?
-
In j.halders Posting stecken einige Detailfehler, dazu möchte noch etwas sagen:
j.halder schrieb:
Der Thread ist ein eigenständiger Process, aber nur solange er sich innerhalb von Execute() oder einer in Execute() aufgerufenen Methode befindet.
Vor dem Einsprung in Execute() und nach dem Verlassen von Execute() ist der Thread ein Teil des Haupt-Processes (Main).Ein Thread ist kein Prozess. Niemals. Ein Thread ist Teil eines Prozesses, aber zwischen Prozess und Thread gibt es himmelweite Unterschiede. Wikipedia liefert da ausführliche Informationen.
j.halder schrieb:
Synchronice() dient dazu, Thread- und Haupt-Process miteinander zu synchronisieren. Beide Processe laufen parallel. Wenn der Thread nun eine Funktion des Haupt-Processes aufruft, dann muss der Haupt-Process zuerst seine aktuelle Arbeit stoppen, Register, Stack etc. sichern, erst dann kann er die vom Thread aufgerufene Methode ausführen.
Für deine Anwendung existiert genau ein Prozess. In diesem Prozess laufen einige Threads, einer davon ist der VCL Hauptthread. So etwas wie Thread-Prozess oder Haupt Prozess existieren nicht, es gibt nur den Prozess.
Der VCL Hauptthread ist für die Abarbeitung der Windows Nachrichten zuständig und steuert die GUI bzw. reagiert auf Benutzereingaben. Du kannst aus einem anderen Thread beliebige Funktionen/Methoden aufrufen, solange du keine GUI Elemente anfasst.j.halder schrieb:
Solange sich der Thread-Process innerhalb von Execute() oder einer in Execute() aufgerufenen Methode befindet, MUSS Synchronize() verwendet werden.
Befindet sich der Thread-Process außerhalb von Execute() (Konstruktor, Destruktor, OnTerminate()), dann darf Synchronize() NICHT verwendet werden.Das ist nur halb wahr. Du darfst
Synchronize
nicht im im VCL Hauptthread aufrufen, weil es zu Deadlocks führen kann, ansonsten existieren keine Einschränken. Es kommt also auf den Thread-Kontext an, nicht auf die Methode, in derSynchronize
aufgerfuen wird.@TE:
Das Problem der Zugriffsverletzung hängt höchstwahrscheinlich damit zusammen, dass du das Objekt, das dir die Windows Nachricht zugeschickt hat, während der Nachrichtenbehandlung löscht. Nach dem Verlassen derMyPanelClick
kehrt die CPU in den Code des Aufrufers zurück, der zu diesem Zeitpunkt aber gelöscht wurde, und das führt zu einer Zugriffsverletzung. Der Ablauf der Nachrichtenbehandlung sieht (stark vereinfacht) etwa so aus:void TObject::Dispatch( void* Message ) { // Zauberei hier, in folge dessen irgendwann // MyPanelClick aufgerufen wird. if( OnClick ) OnClick( this ); // hier passiert noch mehr, aber es führt zu einem Zugriffsfehler, weil dieses // Objekt in deinem OnClick Handler gelöscht wurde und diese Methode auf // Attribute des aktuellen Objekts zugreifen will. }
Du hast ein Timing Problem, du darfst das Panel im OnClick Handler nicht löschen. Ein Möglichkeit wäre z.B., das Panel zum Löschen markieren und in einem Timer Event alle zum Löschen markierte Elemente tatsächlich zu löschen.
-
Danke für die Erläuterungen DocShoe!
Den Wiki-Artikel werd ich mir aich gleich nochmal genauer durchlesen.Das mit der Zugriffsverletzung hört sich mit deiner Erklärung auch logisch an.
Werde mal schauen wie ich das löschen vom Panel sonst lösen kann.
Zunächst kann ich ja statt 'delete' auch 'MyPanel->Visible = false' nutzen um den Panel optisch schonmal loszuwerden.
Danach versuche ich dann das ganze TPanel per 'Freigabe' und mit Timer zu löschen.Edit:
Mit einer Abfrage in der 'Execute()'-Methode und 'bool bDeletePanel' als Variable klappt es ohne zugriffsverletzung.
Danke für dne Tipp und die Erläuterung!
-
Eine Frage bleibt aber immernoch offen:
Wenn ich im Thread ein TPanel erstelle und 'OnClick = MyPanelClick' zuweise,
brauche ich dann 'Synchronize()' für Methoden-Aufrufe aus der 'OnClick()'-Methode heraus?//--------------------------------------------------------------------------- void __fastcall TTrdExecute::MyPanelClick(TObject *Sender) { // Methoden-Aufruf MIt oder OHNE 'Synchronize()' ??? // Test(); // Synchronize(Test); } //---------------------------------------------------------------------------
-
Hi,
nein, vermutlich brauchst du kein
Synchronize
. Der Code gehört zwar zu deiner Thread Klasse, wird aber aus VCL Hauptthread aufgerufen. Es sei denn, du erzeugst das Panel dynamisch in einem deiner Threads (und nicht im VCL Hauptthread). Genau genommen habe ich die Frage schon beantwortet:DocShoe schrieb:
Das ist nur halb wahr. Du darfst Synchronize nicht im im VCL Hauptthread aufrufen, weil es zu Deadlocks führen kann, ansonsten existieren keine Einschränken. Es kommt also auf den Thread-Kontext an, nicht auf die Methode, in der Synchronize aufgerfuen wird.
Du solltest dein Design gut genug kennen, um die Frage zu beantworten.
-
Ja, das TPanel wird dynamisch in einem der Threads erstellt und die 'OnClick()'-Methode wird innerhalb vom Thread zugewiesen.
Code-Beispiel steht auch ein paar Beiträge weiter oben.Kann ich denn davon ausgehen, dass der Klick selbst vom VCL-Hauptthread gesendet wird,
wenn ich den (im Thread erstellten) Panel anklicke?
-
Nein, dann brauchst du vermutlich doch die
Synchronize
Methode.
Nur so aus Neugierde:
Was machen deine Threads eigentlich, dass sie Panels erzeugen und wieder löschen? Hört sich seltsam an...Edit:
Die Textwand habe ich mir nicht angeguckt. tl;dr
-
Ich bastel grad ne Art Geschicklichkeits-Spiel mit Denkaufgaben, Memory-Aufgaben, Geschicklichkeits-Tests, Schnelligkeitstests usw.
Damit ich nicht 100 verschiedene Sachen vorbereiten muss, erstelle ich das pro Aufgabe dynamisch und mit random im Thread
und nach jeder Aufgabe werden die aufgaben-spezifischen Panel wieder gelöscht.Das ganze läuft auf Zeit und mit Punktzahl pro Aufgabe + Zeitbonus;
Am Ende gibts dann localen + online Highscore.---
Kann ich i-wie herausfinden ob ich im VCL-Hauptthread-Kontext arbeite?
-
Da besteht doch gar keine Notwendigkeit, das in einem Thread zu machen...
-
Hallo ihr beiden,
der Click-Event wird auf jeden Fall innerhalb des VCL-Hauptthreads ausgeführt, so daß kein Synchronize nötig ist (egal von welchem Thread aus das Ereignis registriert wurde).
P.S. Sehe ich genauso, daß hier ein Thread unnötig ist -> dies ist doch ein klassischer Fall für einen Timer (und dieser läuft automatisch im VCL-Hauptthread).
-
Aha, danke für die Info Th69. War mir jetzt so nicht klar, ich bin immer davon ausgegangen, dass der Thread, der ein GUI Element erzeugt, auch dessen Message Queue bedient.
CppDude schrieb:
Kann ich i-wie herausfinden ob ich im VCL-Hauptthread-Kontext arbeite?
Wenn du ein Fenster hast, von dem du weißt, dass es im VCL Hauptthread erzeugt worden ist (z.B. alle Formulare, die automatisch erzeugt werden), dann kannst du über die Windows API herausfinden, ob du dich im VCL Hauptthread befindest:
if( ::GetWindowThreadProcessId( Form->Handle ) == ::GetCurrentThreadId() ) { // Thread IDs stimmen überein => wir sind im VCL Hauptthread } else { // Thread IDs stimmen nicht überein => wir sind nicht im VCL Hauptthread }
-
Danke für die Aufklärung wegen dem OnClick.
Ihr meint das wirklich Ernst mit dem "kein Thread notwendig" ??
Die Anwendung würde doch mega "hängen bleiben" wenn ich da 36 TPanel erstelle
und dafür keinen Thread verwenden würde.Nebenbei läuft ja auch noch Sound + Lebensanzeige.
Wie soll das denn mit nem Timer flüssig laufen.Edit: ... werde das mal Testen mit den IDs, danke.
-
Naja, kommt drauf an. Du kannst bei Programmstart alle Panels erzeugen und dann nur das jeweils benötigte anzeigen. Dann hast du beim Programmstart eine kurze Verzögerung, weil die Panels erzeugt werden müssen, ersparst dir aber die ganze Thread Hampelei. Ein normaler Windows Timer löst mit etwa 15ms auf, das sollte für deine Belange mehr als ausreichend sein.
-
Hm... werd ich mir heute mal anschauen. Zumindest werde ich mir ein neues Projekt erstellen
und da mal versuchen das Thema mit dem Timer zu lösen und auf die Threads zu verzichten.Ich glaube noch nicht so wirklich daran, dass sich das "flüssig" abspielen lässt, wenn ich
dann auch noch den Sound nebenbei abspiele und er ständig Berechnungen für Punktzahl und
Lebenspunkte durchführt.Aber ich werde es trotzdem mal versuchen um zu sehen ob die Threads wirklich so uberflüssig
sind für diesen Anwendungsbereich.Schonmal danke für die ganzen Erklärungen und die Hilfe.
Das bring tmich auch für zukünftige Projekte weiter.
-
Hallo nochmals,
durch das Synchronize() hast du ja bisher auch die ganzen Panels im VCL-Hauptthread erzeugt (anders geht es ja auch nicht), d.h. die Umstellung auf den Timer sollte also keine Performancebremse sein
Ich habe mir jetzt noch mal im Detail deinen Thread-Execute() angeschaut:
while (!Terminated) { // Zeitberechnung // ... // aktuelle Laufzeit anzeigen // (wird aller 1000 ms aufgerufen) if ( ::GetTickCount() - TimeStart >= 1000) { Synchronize(ShowZeit); // Startzeitpunkt reset TimeStart = ::GetTickCount(); } // Pause um Prozessorauslastung nicht auf 100% zu treiben Sleep(Pause); }
Du hast damit also nur einen Timer simuliert (und das auch noch äußerst ungünstig, da bis auf die Zeitabfrage eh alles im VCL-Hauptthread abläuft, d.h. der Threadwechsel ist hier teurer als dein eigentlicher Code)!
Fazit: Threads nur dann verwenden, wenn wirklich Background-Aktionen (z.B. längere Berechnungen, Simulationen o.ä) ausgeführt werden sollen. Interaktionen (Animationen, Ein-/Ausblendeffekte etc.) mit dem GUI immer über einen Timer realisieren.
-
Habe jetzt mal ein relativ einfaches Test-Programm geschrieben um mir die Verwendung von 'TTimer' anzuschauen:
- 1 Formular, 1 TPanel zum Anzeigen der Laufzeit
- 2 TTimer (Laufzeit aller 1000 ms; prüfen ob ein Sound abgespielt werden soll aller 25 ms)
- 1 TMediaPlayer zum Abspielen der Sounds// 'UnitMain.cpp' - class TMain - Hauptformular //--------------------------------------------------------------------------- #include <vcl.h> // Visual Command Library (VCL) #pragma hdrstop //--------------------------------------------------------------------------- #include "UnitMain.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" //--------------------------------------------------------------------------- TMain *Main; //--------------------------------------------------------------------------- // Projekt-Start: 16.08.2013 - 13:15 Uhr //--------------------------------------------------------------------------- // letztes Update: 16.08.2013 - 15:40 Uhr //--------------------------------------------------------------------------- // ########################################################################## ### Konstruktor // ### Konstruktor - deklarierte Variablen initialisieren ### // ########################################################################## //--------------------------------------------------------------------------- __fastcall TMain::TMain(TComponent* Owner) : TForm(Owner) { // Programmpfad und ini-Datei Programmpfad = ExtractFilePath(Application->ExeName); iniFile = ChangeFileExt( ExtractFileName(Application->ExeName), ".ini" ); // Sound-File-Ordner SoundFolder = ExtractFilePath(Application->ExeName) + "Sounds\\"; // Programm-Version und letztes Update strVersion = "Vorlage v0.1a"; strLastUpdate = "16.08.2013 - 15:40 Uhr"; // Startzeitpunkt der Anwendung iStartTime = ::GetTickCount(); // Sound-Auswahl für den MediaPlayer - "Intro" Sound = "Intro"; // ini-Datei laden ini_Load(); } //--------------------------------------------------------------------------- // ########################################################################## ### Destruktor // ### Destruktor - Speicher aller initialisierten Variablen freigeben ### // ########################################################################## //--------------------------------------------------------------------------- __fastcall TMain::~TMain() { // ini-Datei speichern ini_Save(); } //--------------------------------------------------------------------------- // ########################################################################## ### ini-Datei Laden / Speichern // ### ini-Datei Laden / Speichern ### // ########################################################################## //--------------------------------------------------------------------------- void __fastcall TMain::ini_Load() { TStringList *ini = new TStringList(); try { ini->LoadFromFile(Programmpfad + iniFile); //----- // Position if (ini->Values["posX"] != "" && ini->Values["posY"] != "") { Main->Left = StrToInt(ini->Values["posX"]); Main->Top = StrToInt(ini->Values["posY"]); } else { // Formular in Bildschirmmitte rücken Main->Left = int(Screen->Width / 2) - int(Main->ClientWidth / 2); Main->Top = int(Screen->Height / 2) - int(Main->ClientHeight / 2); } } catch(...) { // Formular in Bildschirmmitte rücken Main->Left = int(Screen->Width / 2) - int(Main->ClientWidth / 2); Main->Top = int(Screen->Height / 2) - int(Main->ClientHeight / 2); } // Speicher freigeben delete ini; } //--------------------------------------------------------------------------- void __fastcall TMain::ini_Save() { TStringList *ini = new TStringList(); // Position ini->Add("[Position]"); ini->Add("posX=" + String(Main->Left)); ini->Add("posY=" + String(Main->Top)); // ini File speichern try { ini->SaveToFile(Programmpfad + iniFile); } catch (Exception *E) { String msg = "Fehler beim Speichern der ini-Datei: \n"; msg += "\" " + E->Message + " \""; Application->MessageBox( msg.c_str(), strVersion.c_str(), 0+64 ); } // Speicher freigeben delete ini; } //---------------------------------------------------------------------------
// 'UnitLaufzeit.cpp' - class TMain - TTimer (Laufzeit) //--------------------------------------------------------------------------- #include "UnitMain.h" //--------------------------------------------------------------------------- // ########################################################################## ### Timer - Laufzeit der Anwendung // ### Timer - Laufzeit der Anwendung ### // ########################################################################## //--------------------------------------------------------------------------- void __fastcall TMain::LaufzeitTimer(TObject *Sender) { // Laufzeit berechnen int iRunTime = ::GetTickCount() - iStartTime; // Variablen für die Berechnung int h, m, s; // --- // RunTime in Stunden, Minuten und Sekunden umwandeln // Stunden h = int( (iRunTime / 1000) / 60 / 60 ); // Minuten m = int( (iRunTime / 1000) / 60 ) - (h * 60); // Sekunden s = int( (iRunTime / 1000) - (m * 60) ); // Variablen für die Anzeige String H = "", M = "", S = ""; // berechnete Werte zuweisen H = String(h); M = String(m); S = String(s); // Länge der Werte für die Anzeige anpassen - Format: "##" if (h < 10) H = "0" + H; if (m < 10) M = "0" + M; if (s < 10) S = "0" + S; // --- // aktuelle Laufzeit anzeigen AnzZeit->Caption = H + ":" + M + ":" + S; } //---------------------------------------------------------------------------
// 'UnitSound.cpp' - class TMain - TTimer (Sound) //--------------------------------------------------------------------------- #include "UnitMain.h" //--------------------------------------------------------------------------- // ########################################################################## ### Timer - Sound abspielen? // ### Timer - prüfen ob ein Sound abgespielt werden soll ### // ########################################################################## //--------------------------------------------------------------------------- void __fastcall TMain::TimerSoundTimer(TObject *Sender) { if (Sound != "") { // Sound gefunden und abspielen? bool bPlay = true; if (Sound == "Intro") { MediaPlayer->FileName = SoundFolder + "Intro.mp3"; } else { // kein Sound ausgewählt - MediaPlayer nicht starten bPlay = false; } // --- // Sound Reset Sound = ""; // --- // Sound abspielen if (bPlay) { try { MediaPlayer->Open(); MediaPlayer->Play(); } catch (Exception *E) { String msg = "Fehler beim Abspielen des Sounds: \n"; msg += "\" " + E->Message + " \""; Application->MessageBox( msg.c_str(), strVersion.c_str(), 0+64 ); } } // end: if (bPlay) } // end: if (Sound != "") } //---------------------------------------------------------------------------
// 'UnitMain.h' - class TMain - Header //--------------------------------------------------------------------------- #ifndef UnitMainH #define UnitMainH //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ExtCtrls.hpp> #include <MPlayer.hpp> //--------------------------------------------------------------------------- class TMain : public TForm { //------------------------------------------------------------------------- __published: // Von der IDE verwaltete Komponenten // Timer und Panel zum Anzeigen der Laufzeit TTimer *Laufzeit; TPanel *AnzZeit; // Timer und MediaPlayer zum Abspielen von Sounds TMediaPlayer *MediaPlayer; TTimer *TimerSound; // 'OnTimer()'-Methode von TTimer (Laufzeit) void __fastcall LaufzeitTimer(TObject *Sender); // 'OnTimer()'-Methode von TTimer (Sounds) void __fastcall TimerSoundTimer(TObject *Sender); //------------------------------------------------------------------------- private: // Anwender-Deklarationen // Programmpfad und ini-Datei String Programmpfad, iniFile; // ini-File laden/speichern void __fastcall ini_Load(); void __fastcall ini_Save(); // Startzeitpunkt der Anwendung int iStartTime; // --- // Sound-File-Ordner String SoundFolder; // Sound-Auswahl für den MediaPlayer String Sound; //------------------------------------------------------------------------- public: // Anwender-Deklarationen // --- // public-Variablen - Zugriff aus allen Threads/Formularen möglich // Programm-Version und letztes Update String strVersion, strLastUpdate; //----------------------------------------------------------------------- // Konstruktor - deklarierte Variablen initialisieren __fastcall TMain(TComponent* Owner); // Destruktor - Speicher aller initialisierten Variablen freigeben __fastcall ~TMain(); //----------------------------------------------------------------------- }; //--------------------------------------------------------------------------- extern PACKAGE TMain *Main; //--------------------------------------------------------------------------- #endif
Zur Übersichtlichkeit hab ich mal angefangen die 'OnTimer()'-Methoden in eigene Units (*.cpp) zu packen.
Falls dann später noch mehr Funktionen darüber aufgerufen werden, habe ich die direkt beisammen.Aber bis jetzt funktioniert das alles erstmal sehr gut. Muss mal sehen ob ich mein vorhandenes Programm
mit den Threads jetzt so umbauen kann, dass am Ende alles über die 'TTimer' läuft ohne zu Ruckeln.
-
Ich bin gerade immernoch dabei meine "alte Version" mit den Threads komplett auf 'TTimer' umzustellen.
Gerade eben konnte ich nun mal den ersten kleinen Test laufen lassen, bei dem alle 'TTimer' parallel
laufen und Sound abgespielt wird, die Lebenspunkte werden berechnet und angezeigt und eine Aufgabe
gelöst werden muss.Hat alles super geklappt und läuft (unerwartet) flüssig.
Vielen Dank für den Tipp mit dem TTimer!Ich werde bestimmt nochmal 2 bis 3 Stunden brauchen, bis ich das Programm komplett auf TTimer
umgestellt habe, aber der Aufwand lohnt sich hier ja auf jeden Fall. Vorallem habe ich dann nicht
mehr das Problem mit dem 'Synchronize()' und/oder den Zugriffsrechten.
-
Bitte sehr
und noch viel Erfolg bei deinem Projekt.
-
Th69 schrieb:
Bitte sehr
und noch viel Erfolg bei deinem Projekt.
Danke! Es läuft sehr gut bis jetzt.
Die Umstellung auf 'TTimer' hat wirklich ein paar große Verbessungen gebracht.
Ich muss mich nicht mehr mit Zugriffsverletzungen rumschlagen und das Programm läuft jetzt
auch viel schneller. Wenn ich also Sachen dynamisch erstelle und diese dann später wieder
lösche, dann geht das jetzt verdammt schnell und ohne Verzögerung.Ich habe das Projekt jetzt komplett von den Threads befreit und arbeite jetzt nur noch
mit dem 'TTimer' und bin auch schon ein Stück weiter als vor der Umstellung. Bei dieser
Gelegenheit habe ich auch gleich mal ein bisschen aufgeräumt und durch die Umstellung
muss ich nun auch keine Werte mehr an die Threads übergeben, was schneller und besser ist.Gut das ich hier mal meinen Quellcode gepostet habe. Nochmal vielen lieben Dank!
Ich hoffe ich bekomme das Projekt in den nächsten 14 Tagen fertig.