Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Thread ist mit einer Aufgabe fertig: Mainthread mitteilen (https://www.delphipraxis.net/111405-thread-ist-mit-einer-aufgabe-fertig-mainthread-mitteilen.html)

xZise 2. Apr 2008 23:13


Thread ist mit einer Aufgabe fertig: Mainthread mitteilen
 
Hallo DP,
ich habe ein kleines Problem :)
Und zwar löse ich z.Zt. ein Problem Multithreaded. Dazu habe ich einen Thread, der eine Liste von Dateien abarbeitet.
Nun soll dieser dem Mainthread mitteilen, sobald ein Download fertig ist.

Entweder ich polle mit dem Mainthread (=> Unschön, da unnötige Auslastung) ob Aufgaben fertig sind, oder der Thread schickt irgendwie eine Nachricht an den Mainthread.
Das Problem ist das der Thread nicht aufhören soll zu arbeiten, sondern sozusagen nur der Liste sagt: "Bin mit der Datei XYZ fertig" und dann die Liste weiter abarbeitet.

Also ist "Syncronize" ganz schlecht ;)
Was sich anbieten würde, wären Messages, wobei ich da zwei Probleme hätte.
Erstens: Wie schicke ich eine ab, so dass ich die Informationen übermittle und ohne verwechslungen zu begegnen.
Zweitens: Wie empfange ich diese?
Bestimmt einfach zu lösen. ^^

MfG
xZise

Dax 2. Apr 2008 23:19

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Du kannst Synchronize benutzen, das sollte kein Problem darstellen. Dazu schreibst du dir eine Methode in die Threadklasse, die - in einer Critical Section - beim Hauptthread anklopft, sich selbst in die Liste wartender Threads einfügt und sich dann schlafen legt, nachdem du die CS geschlossen und über Synchronize dem Hauptthread Bescheid gesagt hast, dass da was wartet. Der Hauptthread schnappt sich nun die Liste und verteilt neue Aufgaben an die Threads, weckt sie wieder auf und das Spiel geht von vorne los.

Luckie 2. Apr 2008 23:26

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Wie erzeugst du den Thread? Mit der TThread-Klasse der VCL oder mit BeginThread? Wenn ersteres, die Thread-Klasse kennt das Ereignis OnTerminate, welches aufgerufen wird, wenn die Execute Methode verlassen wird. Arbeitest du mit BeginThread, dann schick eine Nachricht an dein Hauptthread (const TM_FINISH = WM_USER + 1). Abfangen kannst du die Nachricht, in dem du die WndProc deiner Form überschreibst und gezielt auf deine Nachricht reagierst.

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure WndProc(var Message: TMessage); override;
  end;

var
  Form1: TForm1;

const
  TM_FINISH = WM_USER +1 ;

type
  TThreadParams = record
    FHandle: THandle;
  end;
  PThreadParams = ^TThreadParams;

implementation

{$R *.dfm}

function Thread(p: Pointer): Integer;
var
  Handle: THandle;
begin
  Handle := PThreadParams(p)^.FHandle;
  SendMessage(Handle, TM_FINISH, 0, 0);
  Result := 0;
  Dispose(p);
end;

procedure TForm1.WndProc(var Message: TMessage);
begin
  inherited;
  case Message.Msg of
    TM_FINISH: ShowMessage('Fertig');
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  tp: PThreadParams;
  ThreadID: Cardinal;
begin
  New(tp);
  tp.FHandle := Handle;
  CloseHandle(BeginThread(nil, 0, @Thread, tp, 0, ThreadID));
end;

end.

xZise 3. Apr 2008 00:26

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Zitat:

Zitat von Dax
Du kannst Synchronize benutzen, das sollte kein Problem darstellen. Dazu schreibst du dir eine Methode in die Threadklasse, die - in einer Critical Section - beim Hauptthread anklopft, sich selbst in die Liste wartender Threads einfügt und sich dann schlafen legt, nachdem du die CS geschlossen und über Synchronize dem Hauptthread Bescheid gesagt hast, dass da was wartet. Der Hauptthread schnappt sich nun die Liste und verteilt neue Aufgaben an die Threads, weckt sie wieder auf und das Spiel geht von vorne los.

Sorry Dax, aber das klingt etwas "konfus" ;)
Also ich habe einen Mainthread (MT) und einen Downloadthread (DT).
Der DT macht also folgendes:
  • CS.Enter
  • Sync(MTMethode)
  • "Liste wartender Threads?"
  • CS.Leave
  • Suspend

Aber was ich möchte ist ja folgendes:
Code:
DT
 |
 |
 |
 Download fertig     MT
 |------------------> Aha :) DL fertig
 Weitere Datei        | (Ein paar Sachen machen)
 downloaden (wenn da) | 
 v                    v
Wobei alles was dort beim MT abläuft im Kontext von "Sync" aufgerufen würde.

Zitat:

Zitat von Luckie
Wie erzeugst du den Thread? Mit der TThread-Klasse der VCL oder mit BeginThread?

Es ist die VCL TThread-Klasse.

Zitat:

Zitat von Luckie
Wenn ersteres, die Thread-Klasse kennt das Ereignis OnTerminate, welches aufgerufen wird, wenn die Execute Methode verlassen wird.

Theoretisch wird sie nie verlassen ;)
Nur wenn das Programm beendet wird.

Aktuell sieht der Code folgendermassen aus:
Delphi-Quellcode:
procedure TDownloadThread.Execute;
var
  i, j: Integer;
  fs : TFileStream;
  fn : string;
  breaked : Boolean;

  FDownload : TIdHTTP;
  FSource, FDestination : string;
  FExtract : Boolean;
begin
  // Initiate Downloader
  FDownload := TIdHTTP.Create(nil);
  try
    // Searching until stopped
    while true do
    begin
      try
        // Searching all entrys
        i := 0;
        while i < FList.Count do
        begin
          // Searching threadsafe
          FList.EnterCritical;
          try
            // Searching for a ready entry or end of the list
            while (i < FList.Count) and (FList[i].Status <> dsReady) do
              Inc(i);

            if i < FList.Count then
            begin
              // Found a file to download
             
              [...] // Only downloadcode
              // Send message to Mainthread: Download complete
            end;
          finally
            FList.LeaveCritical;
          end;
        end;
      finally
        Suspend;
      end;
    end;
  finally
    FreeAndNil(FDownload);
  end;
end;
Wie man sieht, habe ich mehrere Schleifen.
Die äußerste sorgt dafür, dass durch "Resume" der Thread immer wieder angeworfen werden kann.
Die nächst innere sorgt dafür dass er solange sucht, bis er alle Einträge durch ist.
Die innerste sorgt dann dafür dass diejenigen übersprungen werden, die gedownloadet werden, gedownloadet sind, oder noch nicht fertig sind.

Ich habe mich dabei für drei Schleifen entschieden, da ich somit einfach mit den CriticalSections verhindern kann, dass während der Suche nicht an der Liste gearbeitet wird.

MfG
xZise

busybyte 3. Apr 2008 01:04

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Aber Dax hat recht,ohne polling gehts's meiner Meinung nach nur über eine Methode, was aber im Endeffekt beim Hauptthread einem Polling gleich kommt
und der Geschwindigkeitsverlust ist zu vernachlässigen, zumal es sich um einen Download handelt.Die meiste Geschwindigkeit wird, vom Download
und der Programierart abgesehen,bei der grafischen Darstellung der Daten verbraucht.(Refresh,Update,Processmessages usw.,weisst Du sicher selbst)

Dax 3. Apr 2008 01:11

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Ich meine es so:

Downloadthread:

CS.Enter
MT.WartendeThreads.Add(self)
CS.Leave
Synchronize(MT.NeueAufgabe)
Suspend


Hauptthread, NeueAufgabe:

for each (wartender Thread) do
neue Aufgabe zuteilen
Resume

Ansonsten kannst du auch eine Liste mit herunterzuladenden Dateien bereitstellen und es so lösen:

Downloadthread:

CS.Enter
MT.Daten := irgendwas
Synchronize(TeilFertig)
Aufgabe := Dequeue(Aufgaben)
CS.Leave


Wie das TeilFertig-Dings im Hauptthread aussieht, überlasse ich dir ;)

sirius 3. Apr 2008 07:42

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Zitat:

Zitat von Dax
CS.Enter
MT.Daten := irgendwas
Synchronize(TeilFertig)
Aufgabe := Dequeue(Aufgaben)
CS.Leave

Du mischst hier zwei verschiedene Syncronisationsmethoden. Das führt leicht zu einem Stillstand der Anwendung.

Ohne zu bewerten, welche Lösung für dich geeigneter ist, möchte ich dir noch deine eingangs erwähnte Methode über Messages vorstellen:
Du nimmst dir einfach eine dynamische Variable.
Delphi-Quellcode:
type TInfo=....
     PInfo=^TInfo;
Im DownloadThread:
Delphi-Quellcode:
//const myMessage=WM_User;

procedure xyz.execute;
var Info:PInfo;
...
   
  //zum senden
  new(Info)
  Info^:=....
  if not postmessage(FMainHandle,myMessage,0,integer(Info)) then
    dispose(Info); //Message nicht erfolgreich (sollte nicht auftreten)
Damit hast du deine Information in den Heap gelegt und kannst diese im Mainthread asynchron auslesen.
Delphi-Quellcode:
//zur einfacheren Handhabung wäre noch ein eigener Message-Typ möglich:
type TDTMessage=packed record
      Msg:Cardinal;
      WParam:LongInt;
      Info:PInfo;
      Result:LongInt;
     end;


  type TMainForm=...
    procedure getDownloadMessage(var msg:TDTMessage); message myMessage;
    //oder
    procedure getDownloadMessage(var msg:TMessage); message myMessage;

...
procedure TMainform.getDownloadMessage(var msg:TDTMessage);
begin
  try
    irgendetwas:=msg.Info^;
  finally
    dispose(msg.Info);
  end;
end;

Luckie 3. Apr 2008 07:43

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Zitat:

Zitat von xZise
Zitat:

Zitat von Luckie
Wenn ersteres, die Thread-Klasse kennt das Ereignis OnTerminate, welches aufgerufen wird, wenn die Execute Methode verlassen wird.

Theoretisch wird sie nie verlassen ;)
Nur wenn das Programm beendet wird.

Es steht dir doch frei eigene Ereignisse zu deklarieren. Desweiteren kannst du auch mit der Thread-Klasse Nachrichten nutzen. Ist nur nicht so schön, da es in diesem Fall irgendwie das OOP Konzept verletzt.

alzaimar 3. Apr 2008 08:10

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Dafür eignet sich eine Jobliste doch ganz gut. Pro Datei ein Job. Wenn ein Job fertig ist, teilt er dies dem Hauptthread mit.

Du kannst ja mal schauen, ob Du meine Workerthreadpool-Klasse verwenden willst. Ich persönlich finde die Unterteilung in einzelne Jobs übersichtlich, zumal Du mit meiner Threadpoolklasse mehrere Dateien gleichzeitig geladen werden können.
Das wäre Alles, was Du implementieren müsstest:
Delphi-Quellcode:
Type
  TDownLoadJob = Class (TWorkerThreadJob)
  private
   fMyFile : String;
  protected
    Procedure Execute(aThread: TWorkerThread); Override;
  Public
    Constructor Create (aFileName : String);
  End;

Constructor TDownloadJob.Create (aFileName : String);
Begin
  Inherited Create;
  fMyFile := aFileName;
//  UsesCOMObjects := True  // , falls Du eben COM verwendest
End;

Procedure TDownloadJob.DoShowFinished;
Begin
  MainForm.Memo1.Lines.Add(Format('Download der Datei %s beendet',[fMyFile]));
End;

Procedure TDownloadJob.Execute (aThread : TWorkerThread);
Begin
  DoDownloadFile(fMyFile);
  aThread.Synchronize (DoShowFinished);
End;
Und dann einfach:
Delphi-Quellcode:
ThreadPool := TWorkerThreadPool.Create(10); // Erzeugt 10 Threads, die die Jobs parallel abarbeiten
ThreadPool.OnJobException := HandleFinishedDownloads; // 'Exceptionhandler' zuweisen
For i:=0 to slFileList.Count-1 do
  ThreadPool.AddJob (TDownloadJob.Create (slFileList[i]));
Den Rest erledigt der Threadpool. Die paar Millisekunden, die bei der Rückmeldung verloren gehen, machen rein gar nichts, weil wir ja einen Threadpool haben.

Wenn Du das aber selber basteln willst, und Dein Download-Thread auf gar keinen Fall Synchronize aufrufen soll, dann müsstest Du einen zweiten Thread erzeugen, der eine Stringliste und eine Semaphore verwaltet (Oder Messages, is aber -finde ich- uncool). Dieser 2. Thread wartet also nur darauf, das der Download-Thread ihm etwas in seine Stringliste schreibt und die Semaphore anzuppelt, woraufhin dieser dann kurz aufwacht und schön gemächlich per Synchronize dem Hauptthread die Nachricht(en) in eine Listbox, Memo oder sonstwohin schreibt. Das ginge auch mit Suspend/Resume, aber das mag ich nicht.

Edit: Die erwähnte Workerthreadpool-Klasse unterstützt nun die Benachrichtigung aus einem Job heraus, denn meine o.g. Methode bindet den Job ja an das Formular (über die DoShowFinished-Methode), und das ist nicht gut.

xZise 3. Apr 2008 13:08

Re: Thread ist mit einer Aufgabe fertig: Mainthread mitteile
 
Abgesehen von dieser Threadpool sache:
Was macht "Sync" genau?
Ich habe gedacht, dass dies im Kontext des anderen Threads eine Methode ausführt, und solange wartet bis diese Methode ausgeführt ist.

Und genau das ist mein Problem: Es kann sein, dass diese Methode die aufgerufen wird, lange brauchen kann. Und genau dann wäre es sinnvoll, dass der Thread einfach weiterarbeiten kann.

Oder arbeitet "Sync" anders? Und initiert nur die ausführung des Codes im Kontext des anderen Threades, und arbeitet selber währenddessen weiter?

Edit von xZise um 14:16 am 03.04.2008
[edit=0]In der Hilfe steht folgendes:
[equote:e73dcd2f3f="Hilfe zu "Synchronize"]Der Thread wird unterbrochen, während die angegebene Methode im Haupt-Thread ausgeführt wird.
[/edit][/equote:e73dcd2f3f]

MfG
xZise

PS: Zum Threadpool: Eigentlich wollte ich mit dem Thread nur umgehen, dass die Anwendung blockiert wird.


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:36 Uhr.
Seite 1 von 2  1 2      

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