Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi Formular in Thread auslagern (https://www.delphipraxis.net/178908-formular-thread-auslagern.html)

Gruber_Hans_12345 3. Feb 2014 10:24

Formular in Thread auslagern
 
Hallo

Hab schon bisschen rumgesucht und auch schon das eine oder andere gefunden, aber bin mir nicht ganz sicher ob das nun funktionieren kann oder nicht.

Ich habe in meinem Programm einige Funktionen die halt länger dauern, und möchte nun einen Status anzeigen bzw. so eine Marquee oder so, das der user sieht, ok da tut sich noch was.
Da ich aus verschiedenen Probleme heraus dieses längeren Berechnungen nicht in einen Thread auslagern kann/will - möchte ich ein Progressfenster erzeugen,das in einem Thread ausgelagert ist.
Diesem Fenster schicke ich dann per Postmessage die Fortschrittsinfos, aber das Progressfenster soll den Fortschritt automatisch anzeigen/zeichnen.
Geht soetwas oder bekomme ich da probleme mit der VCL?

Ich hätte geplant entweder ein eigenes TApplication Objekt im Thread zu erezugen, oder eine eigene Messageschleife im Thread, bin mir aber ned sicher ob das 100% hinaut (Da so Thread fehler meist nicht immer auftreten ... )

jaenicke 3. Feb 2014 10:28

AW: Formular in Thread auslagern
 
Du kannst mit PostMessage problemlos aus dem Thread heraus arbeiten, solange du mit den beiden Parametern auskommst.

Eine besondere Messageschleife oder so etwas ist nicht erforderlich, weil die Abarbeitung im Hauptthread erfolgt. Da PostMessage die Message asynchron verschickt, arbeitet der Thread auch wie sicherlich gewünscht derweil einfach weiter, aber dennoch ohne den Hauptthread zu stören. Der muss nur dann auf die Message reagieren und die GUI entsprechend mit den neuen Werten aktualisieren.

Uwe Raabe 3. Feb 2014 10:33

AW: Formular in Thread auslagern
 
Wenn ich dich richtig verstanden habe, geht das so nicht! Du kannst lediglich die Berechnung in den Thread auslagern (unter bestimmten Bedingungen gehen auch Datenbankzugriffe). Alle VCL-Zugriffe (z.B. Anzeige des Fortschritts) müssen im Hauptthread stattfinden.

Sir Rufo 3. Feb 2014 10:55

AW: Formular in Thread auslagern
 
Es riecht hier nach Splash-Screen :)

stahli 3. Feb 2014 11:27

AW: Formular in Thread auslagern
 
Ich sehe es wie Uwe.

Wenn Du Deine Berechnung im Mainthread lässt und in der Funktion (Schleife?) keine Neuzeichnung veranlasst wird sich das Formular nicht neu zeichnen.

Du hast wohl nur zwei Möglichkeiten:
a) Berechnung in eigenen Thread auslagern und immer mal durch Syncronisize einen Formularfortschritt veranlassen
b) komplett im Mainthread bleiben und immer mal Application.ProcessMessages aufrufen.

Ansonsten vielleicht noch eine andere Lösung nutzen, wie von Sir Rufo vorgeschlagen.

Der schöne Günther 3. Feb 2014 11:32

AW: Formular in Thread auslagern
 
Als "Quick and Dirty"-Lösung ist der "Splashscreen" doch vielleicht wirklich nicht so übel. Allerdings bliebe doch weiterhin das Problem dass, wenn jemand auf deine "wirkliche" Anwendung klickt und sie schon 2, 3 Sekunden lang hängt, Windows es zu einem "Ghost Window" macht (ausgrauen, "(Reagiert Nicht)" in der Form-Caption anhängen).

In der gleichen Anwendung quasi eine "zweite VCL" aufmachen habe ich allerdings auch noch nie gesehen (muss allerdings nichts heißen). Ohne mir je ein typisches "Threaded Splashcreen"-Beispiel in Delphi angesehen zu haben würde ich es in eine DLL oder eigene exe auslagern und aus deinem Hauptprogramm musst du mittendrin nur das Fensterhandle wissen, an das du mit PostMessage etwas senden möchtest.

Wäre zumindest mein spontaner Gedanke. Aber weiterhin: Die richtige Lösung ist es eh nicht, in der Zeit kann man wahrscheinlich die Arbeit auch vernünftig in einen Thread auslagern und die Oberfläche erst gar nicht einfrieren lassen...

Sir Rufo 3. Feb 2014 12:19

AW: Formular in Thread auslagern
 
Mal so ein kleiner Minimalcode:
Delphi-Quellcode:
var
  LForm : TForm;
  LThread : TThread;
begin
  LForm := TSplashScreen.Create( nil );
  try
   
    LThread := TStartupThread.Create( LForm );
    try

      LForm.ShowModel;
      LThread.WaitFor;

    finally
      LThread.Free;
    end;

  finally
    LForm.Free;
  end;
end;
Delphi-Quellcode:
TStartupThread
muss lediglich am Ende eine Nachricht an die Form schicken, damit die geschlossen wird.
Alles weitere (Progress, Infos, ...) ist Zuckerguss und funktioniert auf die gleiche/ähnliche Art.

Das Erzeugen von VCL-Gedöns ausserhalb des MainThread-Context sollte man aber bleiben lassen.

himitsu 3. Feb 2014 12:49

AW: Formular in Thread auslagern
 
ShowModal?
Bis zum WaitFor kommt der dann ja nicht, falls nicht jemand vorher das Fenster schließt.

Wäre es da nicht andersrum besser?
Delphi-Quellcode:
var
  LForm : TForm;
  LThread : TThread;
begin
  LForm := TSplashScreen.Create(nil);
  try
    LThread := TStartupThread.Create(LForm);
    try
      LForm.Show;
      LThread.WaitFor;
      LForm.Hide;
    finally
      LThread.Free;
    end;
  finally
    LForm.Free;
  end;
end;

// oder

var
  LForm : TForm;
  LThread : TThread;
begin
  LThread := TStartupThread.Create;
  try
    LForm := TSplashScreen.Create(nil);
    try
      LForm.Show;
      LThread.WaitFor;
    finally
      LForm.Free;
    end;
  finally
    LThread.Free;
  end;
end;

Wie schon erwähnt, müssen die VCL-Komponenten im Hauptthread VCL-Thread bleiben.

Man könnte zwar in anderen Threads direkt via WinAPI Formulare nzeigen, aber diese müssen dann komplett in dem Thread erstellt, behandelt und freigegeben werden, inkl. eigener Message-Loop.

Sir Rufo 3. Feb 2014 13:29

AW: Formular in Thread auslagern
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von himitsu (Beitrag 1246505)
ShowModal?
Bis zum WaitFor kommt der dann ja nicht, falls nicht jemand vorher das Fenster schließt.

Wäre es da nicht andersrum besser?
Delphi-Quellcode:
var
  LForm : TForm;
  LThread : TThread;
begin
  LForm := TSplashScreen.Create(nil);
  try
    LThread := TStartupThread.Create(LForm);
    try
      LForm.Show;
      LThread.WaitFor;
      LForm.Hide;
    finally
      LThread.Free;
    end;
  finally
    LForm.Free;
  end;
end;

// oder

var
  LForm : TForm;
  LThread : TThread;
begin
  LThread := TStartupThread.Create;
  try
    LForm := TSplashScreen.Create(nil);
    try
      LForm.Show;
      LThread.WaitFor;
    finally
      LForm.Free;
    end;
  finally
    LThread.Free;
  end;
end;

Wie schon erwähnt, müssen die VCL-Komponenten im Hauptthread VCL-Thread bleiben.

Man könnte zwar in anderen Threads direkt via WinAPI Formulare nzeigen, aber diese müssen dann komplett in dem Thread erstellt, behandelt und freigegeben werden, inkl. eigener Message-Loop.

Bei deiner Variante wird der MainThread aber blockiert
Delphi-Quellcode:
LForm.Show;
// Wir befinden uns im MainThread-Context und da tut sich jetzt nichts mehr, bis der Thread abgearbeitet ist
LThread.WaitFor;
und das ist bei meiner Variante eben genau nicht der Fall.

Zitat:

Zitat von Sir Rufo (Beitrag 1246500)
Delphi-Quellcode:
TStartupThread
muss lediglich am Ende eine Nachricht an die Form schicken, damit die geschlossen wird.

Hier mal so ein Beispiel StartupThread
Delphi-Quellcode:
unit StartupThread;

interface

uses
  FormThreadWait,
  System.SysUtils,
  Classes;

type
  TStartupThread = class( TThread )
  private
    FCloseForm : TThreadProcedure;
    FInfoToForm : TProc<string>;
    procedure InfoToForm( const AInfo : string );
  protected
    procedure DoExecute; virtual;
    procedure Execute; override;
  public
    constructor Create( AForm : TThreadWaitForm );
  end;

implementation

{ TStartupThread }

constructor TStartupThread.Create( AForm : TThreadWaitForm );
begin
  inherited Create( False );
  // Anonyme Methode zum Schließen der Form
  FCloseForm := procedure
    begin
      AForm.Close;
    end;
  FInfoToForm := procedure( AInfo : string )
    begin
      AForm.Label1.Caption := AInfo;
    end;
end;

procedure TStartupThread.DoExecute;
var
  LIdx : Integer;
begin
  InfoToForm( 'Initialisierung...' );
  Sleep( 500 );
  for LIdx := 1 to 20 do
  begin
    InfoToForm( 'Schritt ' + IntToStr( LIdx ) );
    Sleep( 250 );
  end;
end;

procedure TStartupThread.Execute;
begin
  inherited;
  try
    DoExecute;
  finally
    // Form schließen synchronisiert abschicken
    // Queue würde auch gehen, aber was sollen wir in der Zwischenzeit noch machen, wir sind fertig ;o)
    Synchronize( FCloseForm );
  end;
end;

procedure TStartupThread.InfoToForm( const AInfo : string );
begin
  Queue(
      procedure
    begin
      FInfoToForm( AInfo );
    end );
end;

end.
Anwendung im Anhang (kompiliert und als Source)


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:14 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