AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

[Threads] Textureloader - Einige Fragen

Ein Thema von Zwoetzen · begonnen am 20. Nov 2008 · letzter Beitrag vom 21. Nov 2008
Antwort Antwort
Zwoetzen

Registriert seit: 19. Sep 2007
Ort: Ilmenau
93 Beiträge
 
Delphi 2009 Professional
 
#1

[Threads] Textureloader - Einige Fragen

  Alt 20. Nov 2008, 17:48
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:
  • Ich habe gehört, dass man für gemeinsam genutzten Speicher CriticalSections einsetzen muss. Ich denke, dass dies bei meiner FQueue der Fall ist. Doch wie genau muss das hier aussehen? Einfach um die ganzen FQueue-Methoden-Afurufen dieses Enter/Leave-Dingens drumsetzen?
  • Um nicht immer einen neuen Thread anlegen zu müssen, versuche ich, den einen, der bei Programmstart angelegt wird, am Leben zu behalten. Ist die Vorgehensweise, den Thread bei "arbeitslosigkeit" schlafen zu legen und beim Hinzufügen eines neuen Jobs wieder aufzuwekcen elegant oder sollte man sowas vermeiden? ^^ (Will keinen Müll schreiben, deshalb diese Frage )
  • Zum Verständnis: Was genau macht eigentlich Synchronize und wann muss ich es verwenden? Ich habe gelesen, dass das irgendwas mit dem Context des Threads zu tun hat, weswegen das GenTexture() auch mit Synchronize aufgerufen werden muss. Aber inwiefern muss ich andere Methoden ebenfalls damit aufrufen (wie zB meine FinishProc der Jobs, SyncSucceeded und SyncFailed)?
  • Wie ist das, wenn ich den Thread am Ende wieder freigeben will: Mein logischer Verstand sagt mir, dass ein Terminate() allein nicht reichen kann, da der Thread ja eventuell noch schläft. Muss ich also vorher ein Resume() aufrufen, und danach ein Terminate(), oder andersrum? (Bei ersterem könnte es ja passieren, dass der Thread sofort wieder wegnickt, weil die FQueue ja noch leer ist. Bei zweiterem bin ich aber nicht sicher, ob das richtig ist... )


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;
  Mit Zitat antworten Zitat
Benutzerbild von sirius
sirius

Registriert seit: 3. Jan 2007
Ort: Dresden
3.443 Beiträge
 
Delphi 7 Enterprise
 
#2

Re: [Threads] Textureloader - Einige Fragen

  Alt 20. Nov 2008, 18:06
  1. Ja, du musst bei der Queue Critical Sections verwenden (ich vermute, du gibst dem Objekt im Maintthread neue Aufgaben). So, wie ich das sehe, kannst du anstatt der Queue auch eine TThreadList nehmen. Ansonsten musst du die Queue in eine Klasse mit einer Critical Section kapseln. Da gibt es auch schon vorbereitete Klassen in der SyncObj-Unit.
  2. Wenn du nur einen Job zu gleichen Zeit hast, finde ich diese Vorgehensweise i.O. Du solltest hier nur nicht mit suspend und resume arbeiten, sondern den Thread mittels Events warten lassen (waitforsingleEvent). Dieses Event könntest du in deine Warteschlange mit rein implementieren. Dadurch kannst du es automatisch feuern, sobald estwas der Warteschlange hinzugefügt wird.
  3. Du hast Synchronize richtig verwendet. Achte darauf, dass synchronize nur funktioniert, wenn du es in einer EXE hast (nicht in eine DLL legen) Synchronize hält deinen Thread an und sagt dem Application-Objekt, des es demnächst mal die übergebene Methode starten soll. Und wenn das Application-Objekt damit fertig ist, kann dein Thread weitermachen. Dadurch ist eben sichergestellt, dass dein Thread nicht gleichzeitig mit dem Mainthread arbeitet. Der Mainthread kann so gefahrlos auf den Speicher des Threads zugreifen.
  4. Ja. Wenn du Punkt zwei umsetzt kannst du ja auch das Event feuern. Oder ein zweites Anlegen.
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat
Zwoetzen

Registriert seit: 19. Sep 2007
Ort: Ilmenau
93 Beiträge
 
Delphi 2009 Professional
 
#3

Re: [Threads] Textureloader - Einige Fragen

  Alt 20. Nov 2008, 19:22
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:
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;
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 )
Nur soviel habe ich herausgefunden: WaitForSingleEvent gibts nicht, sollte wohl WaitForSingleObject heißen
  Mit Zitat antworten Zitat
Benutzerbild von sirius
sirius

Registriert seit: 3. Jan 2007
Ort: Dresden
3.443 Beiträge
 
Delphi 7 Enterprise
 
#4

Re: [Threads] Textureloader - Einige Fragen

  Alt 20. Nov 2008, 23:26
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:
 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;
Ich denke, ich habe nix vergessen.
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat
Zwoetzen

Registriert seit: 19. Sep 2007
Ort: Ilmenau
93 Beiträge
 
Delphi 2009 Professional
 
#5

Re: [Threads] Textureloader - Einige Fragen

  Alt 21. Nov 2008, 12:25
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
  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 14:57 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz