|
Antwort |
Registriert seit: 19. Sep 2007 Ort: Ilmenau 93 Beiträge Delphi 2009 Professional |
#1
Hi alle zusammen,
ich bin gerae dabei, eine OpenGL-Anwendung zu schreiben. In diesem Programm müssen je nach aktueller Situation Texturen nachgeladen werden (eben die, die als nächstes gebraucht werden). Das ganze möchte ich (bzw. habe ich schon) in einen Thread auslagern, um das Programm selbst nicht unnötig zu stören. Unten habt ihr den entsprechenden Quellcode von meiner TTextureLoader-Klasse. Meine Fragen diesbezüglich sind nun folgende:
Würde mich freuen, wenn ihr ein paar erklärende Worte niederschreiben könntet, damit ich zum einen keine Fehler einbaue, und zum anderen den Sachverhalt Threads wieder ein wenig mehr verstehe MfG Zwoetzen ____________________________________
Delphi-Quellcode:
type
TFinishProc = procedure(Succeeded: Boolean) of Object; TJob = class Path: String; Texture: TglBitmap2D; OnFinish: TFinishProc; end; TTextureLoader = class(TThread) private FQueue: TObjectQueue; // Speichert die aktuellen Aufträge FCurJob: TJob; // Aktueller Job (für die Synchronize-Prozeduren) procedure SyncGenTex; procedure SyncFailed; procedure SyncSucceeded; public constructor Create; destructor Destroy; override; procedure AddTexture(Path: String; Texture: TglBitmap2D; OnFinish: TFinishProc = nil); procedure Execute; override; end; [...] procedure TTextureLoader.AddTexture(Path: string; Texture: TglBitmap2D; OnFinish: TGFinishProc = nil); // Das Texture-Objekt wird immer schon vor Aufruf dieser Funktion angelegt. // OnFinish bietet die Möglichkeit, "Bescheid" zu geben, wenn die Textur fertig ist oder nicht geladen werden kann var Job: TJob; begin if FileExists(Path) and Assigned(Texture) then begin Job := TJob.Create; Job.Path := Path; Job.Texture := Texture; Job.OnFinish := OnFinish; // Brauch ich für diese Anweisung jetzt eine CriticalSection? (Ich denk mal schon) FQueue.Push(Job); // Falls Thread angehalten wurde, wieder aufnehmen if Suspended then Resume; end; end; procedure TTextureLoader.Execute; begin while not Terminated do begin // Schlafen legen, wenn keine Jobs mehr vorhanden sind, um den Thread zu erhalten if (FQueue.Count = 0) then Suspend else begin // Hier auch eine CriticalSection? FCurJob := MTGJob(fQueue.Pop); // Eventuell noch ein try..finally rumbasteln, um FCurJob sicher freizugeben und einen Fehler in SyncFailed abzufangen try FCurJob.Texture.LoadFromFile(FCurJob.Path); Synchronize(SyncGenTex); Synchronize(SyncSucceeded); FCurJob.Free; except on E: Exception do Synchronize(SyncFailed); end; // try..except end; // if..then..else end; // while end; procedure TTextureLoader.SyncGenTex; begin FCurJob.Texture.GenTexture; end; // Inwiefern sind die folgenden zwei Synchronize-Prozeduren wirklich erforderlich? procedure TTextureLoader.SyncFailed; begin if Assigned(FCurJob.OnFinish) then FCurJob.OnFinish(False); end; procedure TTextureLoader.SyncSucceeded; begin if Assigned(FCurJob.OnFinish) then FCurJob.OnFinish(True); end; |
Zitat |
Registriert seit: 3. Jan 2007 Ort: Dresden 3.443 Beiträge Delphi 7 Enterprise |
#2
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
|
Zitat |
Registriert seit: 19. Sep 2007 Ort: Ilmenau 93 Beiträge Delphi 2009 Professional |
#3
Danke für die Antwort, Sirius
Punkt 3 ist gut erklärt, jetzt habe ich eine bessere Vorstellung, was im Hintergrund beim Aufruf von Synchronize passiert. Zu den drei anderen Punkten: Ich habe jetzt mal eine TThreadQueue (eigentlich eher eine TThreadObjectQueue, is aber zu lang xD) erstellt, die die CriticalSections enthält, nur bin ich nicht sicher, ob ich das so richtig verstanden und umgesetzt habe. (Hab versucht, mich an die TThreadList zu halten) Vor allem beim Destroy bin ich mir unsicher: Wer wäre jetzt für das Freigeben der noch vorhandenen Jobs verantwortlich? Die ThreadQueue selbst, weil sie diese ja "in Bearbeitung" hat, oder der TextureLoader, der die Jobs angelegt hat? (Hatte mal was gehört von dort Freigeben, wo sie angelegt wurden, demnach müsste sich der TextureLoader drum kümmern.)
Delphi-Quellcode:
Des weiteren verstehe ich den Punkt mit den Events nicht wirklich. Habe noch nie mit Events gearbeitet, und weiß somit nicht, wie ich das jetzt machen muss. Könntest du (oder jemand anderes ) einen Beispielcode geben? (Oder direkt in meine ThreadQueue einbauen, wäre noch besser )
type
TThreadQueue = class(TObjectQueue) private FLock: TRTLCriticalSection; public constructor Create; destructor Destroy; override; function Push(AObject: TObject): TObject; function Pop: TObject; function Peek: TObject; end; [...] constructor TThreadQueue.Create; begin inherited Create; InitializeCriticalSection(FLock) end; destructor TThreadQueue.Destroy; begin EnterCriticalSection(FLock); try // Entweder die ThreadQueue kümmert sich ums freigeben, oder der TextureLoader muss es... while (List.Count <> 0) do (inherited Pop).Free; inherited Destroy; finally LeaveCriticalSection(FLock); DeleteCriticalSection(FLock); end; end; function TThreadQueue.Push(AObject: TObject): TObject; begin EnterCriticalSection(FLock); try Result := inherited Push(AObject); finally LeaveCriticalSection(FLock); end; end; function TThreadQueue.Pop: TObject; begin EnterCriticalSection(FLock); try Result := inherited Pop; finally LeaveCriticalSection(Flock); end; end; function TThreadQueue.Peek: TObject; begin EnterCriticalSection(FLock); try Result := inherited Peek; finally LeaveCriticalSection(FLock); end; end; Nur soviel habe ich herausgefunden: WaitForSingleEvent gibts nicht, sollte wohl WaitForSingleObject heißen |
Zitat |
Registriert seit: 3. Jan 2007 Ort: Dresden 3.443 Beiträge Delphi 7 Enterprise |
#4
Ahja, WaitforSingleObject. Man kann ja auch auf Threads, Processe, Semaphore ... warten.
Ich würde die ThreadObjectQueue im Textureloader erstellen und zerstören. Ich bastel mal alles zusammen und baue ein Event ein. Keine Garantie, dass es direkt so funktioniert
Delphi-Quellcode:
type
TThreadQueue = class(TObjectQueue) private FLock: TRTLCriticalSection; //Es gibt auch die Klasse TCriticalSection, aber es besteht kein Unterschied zur direkten Verwendung der WinAPI-Funktionen FEvent: TEvent; //Wie bei der Critical Section, kannst du hier auch direkt die WinAPI nutzen; in der Klasse TEvent sind ein paar Vereinfachungen public constructor Create; destructor Destroy; override; function Push(AObject: TObject): TObject; function Pop: TObject; function Peek: TObject; property Event:TEvent read FEvent; end; [...] constructor TThreadQueue.Create; begin inherited Create; InitializeCriticalSection(FLock) FEvent:=TEvent.create; //evtl. initialisieren? end; destructor TThreadQueue.Destroy; begin EnterCriticalSection(FLock); try while (List.Count <> 0) do (inherited Pop).Free; FEvent.free; inherited Destroy; finally LeaveCriticalSection(FLock); DeleteCriticalSection(FLock); end; end; function TThreadQueue.Push(AObject: TObject): TObject; begin //neues Objekt in der Klasse, also Event feuern EnterCriticalSection(FLock); try Result := inherited Push(AObject); FEvent.SetEvent; finally LeaveCriticalSection(FLock); end; end; function TThreadQueue.Pop: TObject; begin EnterCriticalSection(FLock); try Result := inherited Pop; finally LeaveCriticalSection(Flock); end; end; function TThreadQueue.Peek: TObject; //Was jetzt peek macht, weis ich nicht, deswegen ändere ich hier nix begin EnterCriticalSection(FLock); try Result := inherited Peek; finally LeaveCriticalSection(FLock); end; end;
Delphi-Quellcode:
Ich denke, ich habe nix vergessen.
TTextureLoader = class(TThread)
private FQueue: TThreadQueue FCurJob: TJob; // Aktueller Job (für die Synchronize-Prozeduren) procedure SyncGenTex; procedure SyncFailed; procedure SyncSucceeded; public constructor Create; destructor Destroy; override; procedure AddTexture(Path: String; Texture: TglBitmap2D; OnFinish: TFinishProc = nil); protected procedure DoTerminate; override; //Hier muss noch das Event gesetzt werden, sondet endet der Thread nicht procedure Execute; override; end; [...] procedure TTextureLoader.AddTexture(Path: string; Texture: TglBitmap2D; OnFinish: TGFinishProc = nil); // Das Texture-Objekt wird immer schon vor Aufruf dieser Funktion angelegt. // OnFinish bietet die Möglichkeit, "Bescheid" zu geben, wenn die Textur fertig ist oder nicht geladen werden kann var Job: TJob; begin if FileExists(Path) and Assigned(Texture) then begin Job := TJob.Create; Job.Path := Path; Job.Texture := Texture; Job.OnFinish := OnFinish; FQueue.Push(Job); // Falls Thread angehalten wurde, wieder aufnehmen //das macht jetzt die Queue. Du kannst natürlich SetEvent hier aufrufen und generell in die ThreadKlasse legen; weis nicht, was besser ist. end; end; procedure TTextureLoader.Execute; begin while not Terminated do begin // Schlafen legen, wenn keine Jobs mehr vorhanden sind, um den Thread zu erhalten if (FQueue.Count = 0) then FQueue.Event.Waitfor(infinite); // = waitforsingleobject ohne Zeitbegrenzung //hier bei nochmal auf terminated abfragen else begin //hier evtl. Event.ResetEvent aufrufen FCurJob := MTGJob(fQueue.Pop); // Eventuell noch ein try..finally rumbasteln, um FCurJob sicher freizugeben und einen Fehler in SyncFailed abzufangen try FCurJob.Texture.LoadFromFile(FCurJob.Path); Synchronize(SyncGenTex); Synchronize(SyncSucceeded); FCurJob.Free; except on E: Exception do Synchronize(SyncFailed); end; // try..except end; // if..then..else end; // while end; procedure TTexturLoader.DoTerminate; begin inherited; FQueue.Event.setevent; end;
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
|
Zitat |
Registriert seit: 19. Sep 2007 Ort: Ilmenau 93 Beiträge Delphi 2009 Professional |
#5
Danke, hat mir sehr geholfen.
Irgendwie werden nur die Texturen jetzt gar nicht mehr geladen Mir fehlt aber jetzt auch die Zeit, um alles zu prüfen, werde es die nächsten tage nochmal durchgehen Zu Peek: Neben Pop und Push hat eine Queue auch Peek (oder Top) als Methode, womit man das nächste Element anschauen kann, ohne es direkt mit Pop aus der Queue rauschmeißen zu müssen. Dh das Event muss nicht gefeuert werden, weil nix an der Queue verändert wird Ich brauche es hier nicht wirklich, habe es nur der Vollständigkeits halber mit hingeschrieben ^^ Zu deinem Kommentar im Execute-Thread: Wenn er vom Warten zurückkehrt, prüft er doch automatisch durch die umgebende Schleife auf Terminated, sodass eine zusätzliche Prüfung eigentlich nicht nötig sein sollte
Delphi-Quellcode:
____________________
procedure TTextureLoader.Execute;
begin while not Terminated do begin if (FQueue.Count = 0) then FQueue.Event.Waitfor(INFINITE); // Wenn er zurückkehrt, ist entweder ein neuer Job da, oder der Thread soll beendet werden. // Da dies beim nächsten Schleifendurchlauf getestet wird, brauche ich es nicht extra zu behandeln ;) // Wobei Terminated Vorrang hat, da zuerst die Schleife, und danach erst die IF-Anweisung geprüft wird else begin [...] end; end; // while end; EDIT: Hab den Fehler gefunden: Wenn man natürlich auch weiterhin den TextureLoader-Thread mit inherited Create(True); beim Anlegen suspendiert, kann er ja nie zum Zuge kommen. Kaum macht man's richtig, schon funktioniert's Danke nochmals für deine Hilfe, Sirius |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |