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 Events in TThread (https://www.delphipraxis.net/78521-events-tthread.html)

DocE 6. Okt 2006 11:47


Events in TThread
 
Hallo miteinander!

Es stellt sich folgende Problematik:

In einem TThread werte ich Audiosignale aus. Hierzu verwende ich in dem Thread eine Komponente, die die Arbeit übernimmt und ein Ereignis auslöst, sobald der Puffer voll ist. Damit die Events überhaupt abgearbeitet werden, muss ich Application.ProcessMessages in einer Schleife aufrufen.

Das Problem ist, dass es dadurch gelegentlich zu Fehlern kommt, wenn ich im Hauptthread z.B. ein Fenster erzeuge. Der häufigste Fehler ist: "Leinwand/Bild erlaubt kein Zeichnen", selbst wenn ich die Übergabe des Threads an den Hauptthread komplett ausschalte, also der Thread für sich alleine läuft.

Nehme ich das Application.ProcessMessages raus, kommt es zu keinem Fehler mehr, allerdings wird auch das Event nicht mehr abgearbeitet. Es ist also davon auszugehen, dass der Hauptthread durch den Aufruf von Application.ProcessMessages irgendwie beeinflusst wird. Scheinbar sind dies aber nur spezielle Events, da ich in vorherigen Tests z.B. die Verarbeitung von Timer-Events im Hauptthread durch den Aufruf von Application.ProcessMessages in einem anderen Thread ausschließen konnte.

Meine Fragen also:

1. Warum kommt es durch den Aufruf von Application.ProcessMessages im TThread zu einem Problem?
2. Wie kann man das ganze umgehen? Ist MsgWaitForMultipleObjects() evtl. eine Lösung?

Nach meinem Verständnis müsste man den Thread irgendwie dazu bringen, in bestimmten Abständen seine eigene MessageQueue abzuarbeiten, nur wie?


Execute sieht also derzeit so aus:

Delphi-Quellcode:
procedure TAudioThread.Execute;
begin

 (...)

  AudioKomponente := TAudioKomponente.Create(nil);
  AudioKomponente.OnBufferFilled := BufferFilled;

 (...)

  AudioKomponente.Start;

  while not Terminated do
  begin

    sleep(100);
    Application.ProcessMessages;

  end;

  AudioKomponente.Stop;
  AudioKomponente.Free;

end;


Vielen Dank schon mal!


Grüsse
...Doc

chaosben 6. Okt 2006 12:11

Re: Events in TThread
 
Ein Zauberwort heißt "Synchronize" (OH). Damit sollten sich einige Probleme erübrigen.

xaromz 6. Okt 2006 12:14

Re: Events in TThread
 
Hallo,

NIEMALS Application.ProcessMessages in einem Thread aufrufen!
Wozu auch? Ein Thread läuft ja neben dem Hauptthread, deshalb werden normalen Events auch ganz normal abgearbeitet.

Wenn Du eine Komponente innerhalb eines Threads erzeugst, dann muss auch die gesamte Kommunikation innerhalb des Threads ablaufen. Zeig mal etwas Code, dann kann findet sich bestimmt eine Lösung.

Gruß
xaromz

Bernhard Geyer 6. Okt 2006 12:14

Re: Events in TThread
 
Zitat:

Zitat von chaosben
Ein Zauberwort heißt "Synchronize" (OH). Damit sollten sich einige Probleme erübrigen.

Abe ein Synchronize ein einem Thread so eingebaut macht den Thread u.U. vollkommen unnötig.
Es ist evtl. besser den Code er bei Application.ProcessMessages bearbeitet wird einfach nochmal als (lokale) Funktion zu kopieren so das nur Win32-API-Aufrufe stattfinden.

DocE 6. Okt 2006 12:15

Re: Events in TThread
 
Ich denke Synchronize wird das Problem nicht lösen, denn ich möchte ja gar nichts im Hauptthread ausführen. Das Application.ProcessMessages (bzw. die jetzt gesuchte Alternative) soll ja nur dafür sorgen, dass der Thread seine eigenen Events behandelt.

Die Übergabe an den Hauptthread erfolgt dann unter Verwendung von CriticalSections. Die Übergabe ist aber auch nicht das Problem.

Grüsse
...Doc

DocE 6. Okt 2006 12:22

Re: Events in TThread
 
Hi Xaromz,

der wichtigeste Teil des Codes ist eigentlich oben gepostet. Die AudioKomponente ruft halt das Ereignis OnBufferFilled auf, sobald der Puffer voll ist. Den Verarbeite ich dann und übergebe ihn ggf. an den Hauptthread.

Das Problem ist ja nur diese Schleife:

Delphi-Quellcode:
  while not Terminated do
  begin

    sleep(100);
    Application.ProcessMessages;

  end;
In dieser Schleife muss irgendetwas anderes passieren (anstatt Application.ProcessMessages) damit der Thread seine "eigenen" Events, hier: OnBufferFilled der AudioKomponente, bearbeiten kann.

Ohne Application.ProcessMessages passiert gar nichts (keine Bearbeitung des Events).


Grüsse
...Doc

chaosben 6. Okt 2006 12:25

Re: Events in TThread
 
Zitat:

Zitat von DocE
denn ich möchte ja gar nichts im Hauptthread ausführen.

Das kann nicht sein. Scheinbar führst du irgendwelche Funktionen aus, die Effekte auf das Aussehen visueller Komponenten haben, die zu dem Hauptthread gehören. Wenn diese "Statusanzeigen" synchronisisert werden, wirst du die damit verbundene Fehlermeldung("Leinwand/Bild erlaubt kein Zeichnen") los und hast trotzdem die Vorteile eines Threads.

Natürlich darfst du nicht die ganze Berechnung synchronisieren, denn dann hätte Bernhard recht.

Und btw.: "BufferFilled" sollte idealerweise eine Prozedur des Threads sein.

DocE 6. Okt 2006 12:30

Re: Events in TThread
 
Die einzige Aktion die ich im Hauptthread ausführe ist die Übergabe an ein Array. Es wird nichts gezeichnet, geupdatet o.ä. Aber, wie bereits gesagt, ist die Übergabe auch nicht das Problem.

BufferFilled ist eine Prozedur des Threads.

Die Fehlermeldung kommt durch das "Application.ProcessMessages" in der Schleife. Selbst wenn die Übergabe an den Hauptthread komplett herausgenommen wird, kommt es zu dem Fehler "Leinwand/Bild erlaubt kein Zeichnen", wenn z.B. im Hauptthread ein Fenster geöffnet wird.

Ich glaube ich mache mal ein einfaches Beispielprogramm fertig...

chaosben 6. Okt 2006 12:32

Re: Events in TThread
 
Zitat:

Zitat von DocE
Ich glaube ich mache mal ein einfaches Beispielprogramm fertig...

:hello:

xaromz 6. Okt 2006 12:39

Re: Events in TThread
 
Hallo,

Du erstellst im Thread ein Objekt, das Audiodaten verarbeitet und dabei ab und zu einen Event feuert. Auf diesen möchtest Du innerhalb des Events reagieren.

Sehe ich das richtig?

Wenn ja, dann stellt sich die Frage, wie das Objekt die Audiodaten verarbeitet. Wenn das synchron abläuft, sollte die Lösung ungefähr so aussehen:

Delphi-Quellcode:
type
  TAudioThread = class(TThread)
  private
    procedure OnBufferFilled(Sender: TObject);
  public
    procedure Execute; override;
  end;

implementation

procedure TAudioThread.Execute;
var
  AudioKomponente: TAudioKomponente;
begin
  AudioKomponente := TAudioKomponente.Create(nil);
  AudioKomponente.OnBufferFilled := OnBufferFilled;
  AudioKomponente.Start;
  // hier wird periodisch der Event aufgerufen...
  AudioKomponente.Stop;
  AudioKomponente.Free;
end;

procedure TAudioThread.OnBufferFilled(Sender: TObject);
begin
  Tu irgendwas...
end;
Dadurch, dass die Audiokomponente im Thread aufgerufen wird, läuft sie ja schon neben dem Hauptthread, und der Eventhandler wird im Kontext des Threads aufgerufen.

Wenn hingegen die Audiokomponente asynchron läuft, dann hast Du mit einem Thread ein Problem. Aber dann benötigst Du auch keinen.

Gruß
xaromz

DocE 6. Okt 2006 14:27

Re: Events in TThread
 
So, zunächst zu :

Zitat:

Wenn hingegen die Audiokomponente asynchron läuft, dann hast Du mit einem Thread ein Problem. Aber dann benötigst Du auch keinen.
Da bin ich anderer Meinung. Also die Audiokomponente läuft asynchron (soweit ich das beurteilen kann). Sie löst immer dann, wenn der Puffer voll ist ein Event aus (reagiert intern auf MM_WIM_DATA) und dann wieder beim nächsten vollen Puffer usw. (ohne den Prozessablauf anzuhalten). Dennoch möchte ich die Verarbeitung in einem Thread durchführen, damit 1. der Hauptthread während der Verarbeitung des Puffers nicht hängt, 2. bei hängendem Hauptthread die Verarbeitung der Audiodaten nicht unterbrochen wird. Der Thread hat zusätzlich eine höhere Priorität. Ich hoffe Du stimmst / Ihr stimmt mir da zu.

Jetzt zum eigentlichen Problem:

Als ich versuchte, ein einfaches Programm nachzubauen und anstatt der Audiokomponente einen einfachen Timer einzusetzen, kam es - egal was ich machte - nicht mehr zu dem Fehler. (Sowas ärgerliches :wink: ). Also habe ich mich auf die Suche gemacht und bin (hoffentlich) fündig geworden:

Die Audiokomponente erstellt ein CallBackWin (TWinControl), welches für die Verarbeitung der MM Messages verantwortlich ist.

Delphi-Quellcode:
TCallBackWin = Class(TWinControl)
  private
    { Private declarations }
    AudioComponent : PAudioComp;

    procedure BufferFinished(var Msg: TMessage); message MM_WIM_DATA;
    procedure WaveOpenIn(var Msg: TMessage); message MM_WIM_OPEN;
    procedure WaveCloseIn(var Msg: TMessage); message MM_WIM_CLOSE;

  end;
Dieses wurde ursprünglich erzeugt mit:

Delphi-Quellcode:
  CallBackWin := TCallBackWinIn.CreateParented(TWinControl(Owner).Handle);
  CallBackWin.Visible := false;
  CallBackWin.AudioComponent := @TS;

  iErr := WaveInOpen(@WaveHandle, FWaveDevice, @FWaveFmtEx, Integer(CallBackWin.Handle),
                      0, CALLBACK_WINDOW or WAVE_ALLOWSYNC );
Da ein Thread keinen TWinControl-Owner hat, habe ich das ganze abgewandelt in:

Delphi-Quellcode:
 
  FWindowHandle := Classes.AllocateHWnd(WndProc); // <- Neu

  CallBackWin := TCallBackWinIn.CreateParented(FWindowHandle); // <- Geändert
  CallBackWin.Visible := false;
  CallBackWin.AudioComponent := @TS;

  iErr := WaveInOpen(@WaveHandle, FWaveDevice, @FWaveFmtEx, Integer(CallBackWin.Handle),
                      0, CALLBACK_WINDOW or WAVE_ALLOWSYNC );
Die WndProc war einfach leer.

Das Problem ist (war) jetzt wohl, dass überhaupt dieses CallBackWin, also ein TWinControl im TThread erzeugt ist und Messages verarbeitet (oder sonst was Problematisches vornimmt). Mir ist wohl das Problem bekannt, dass beim Erzeugen eines Formulars in einem Thread der gleiche Fehler auftritt: "Leinwand/Bild erlaubt kein Zeichnen". Deshalb dachte ich mir: Wozu überhaupt dieses CallBackWin, wenn ich ja jetzt eine WndProc-Prozedur habe. Gesagt, getan: CallBackWin entfernt und Ereignisbehandlung in WndProc gepackt:

Delphi-Quellcode:
  FWindowHandle := Classes.AllocateHWnd(WndProc); // <- Neu

  iErr := WaveInOpen(@WaveHandle, FWaveDevice, @FWaveFmtEx, Integer(FWindowHandle), // <- Geändert
                      0, CALLBACK_WINDOW or WAVE_ALLOWSYNC );

Jetzt geht's und der Fehler tritt nicht mehr auf. Allerdings verwende ich immer noch das Application.ProcessMessages in der Schleife des Threads. Scheint also kein Problem darzustellen. Meine Tests haben bisher auch ergeben, dass das im Thread aufgerufene Application.ProcessMessages auf den Hauptthread keine Auswirkungen hat. Vielleicht bedeutet das aber auch nur, dass die Messages für den Hauptthread verloren gehen? Mir fällt jedenfalls nichts auf.


An dieser Stelle wäre nur noch das Problem zu klären:

1. Ob Application.ProcessMessages (im Thread aufgerufen) sich irgendwie schadhaft/problematisch auswirkt.
2. if 1 then :-D Wie können wir das Problem umgehen? Sprich: "Eigene" MessageBehandlung innerhalb des Threads ermöglichen.


Das warten auf die Events in dem Thread läuft nämlich immer noch so:

Delphi-Quellcode:
while not Terminated do
  begin

    sleep(100);
    Application.ProcessMessages;

  end;

Vielleicht habt ihr ja noch eine Idee.


Grüsse
...Doc

xaromz 6. Okt 2006 14:51

Re: Events in TThread
 
Hallo,
Zitat:

Zitat von DocE
Ich hoffe Du stimmst / Ihr stimmt mir da zu.

Nein.
Da die Audio-Komponente asynchron arbeitet, solltest Du diese im Hauptthread erstellen und nur das Verarbeiten des Puffers in einen Thread packen. Das wäre eine saubere Lösung.
Zitat:

Zitat von DocE
An dieser Stelle wäre nur noch das Problem zu klären:

1. Ob Application.ProcessMessages (im Thread aufgerufen) sich irgendwie schadhaft/problematisch auswirkt.
2. if 1 then :-D Wie können wir das Problem umgehen? Sprich: "Eigene" MessageBehandlung innerhalb des Threads ermöglichen.

1: Application.ProcessMessages arbeitet sämtliche Nachrichten des Hauptthreads im Kontext Deines Threads ab. Ob es dann kracht oder nicht ist reines Glück.
2: Wie gesagt, ich würde nur die reine Verarbeitung in einen Thread auslagern, dann stellt sich diese Frage nicht mehr.

Gruß
xaromz

DocE 6. Okt 2006 15:02

Re: Events in TThread
 
Mahlzeit,

Zitat:

Nein.
Da die Audio-Komponente asynchron arbeitet, solltest Du diese im Hauptthread erstellen und nur das Verarbeiten des Puffers in einen Thread packen. Das wäre eine saubere Lösung.
An diese Möglichkeit hatte ich bereits gedacht, diese allerdings ausgeschlossen, weil wenn der Hauptthread hängt, gehen Puffer verloren. Das Event wird ja erst dann ausgeführt, wenn der Hauptthread wieder "idle" ist. Einfaches Beispiel:

Wird ein Sleep(10000) im Hauptthread ausgeführt (oder eine Operation, die solange dauert), wird erst im Anschluss daran, also nach 10 Sekunden das Ereignis "BufferFilled" im Hauptthread ausgeführt. Dann sind mir aber beim nächsten Event schon 9,9 Sekunden Puffer verloren gegangen.

Stellt also keine wirkliche Alternative dar.

Zitat:

Application.ProcessMessages arbeitet sämtliche Nachrichten des Hauptthreads im Kontext Deines Threads ab. Ob es dann kracht oder nicht ist reines Glück.
Kannst Du mir ein Beispiel geben, wann das z.B. problematisch werden kann. Ich konnte das bisher nämlich nicht nachvollziehen, dass es zu Problemen kommt.


Grüsse
...Doc

xaromz 6. Okt 2006 15:11

Re: Events in TThread
 
Hallo,
Zitat:

Zitat von DocE
Stellt also keine wirkliche Alternative dar.

Da Deine Komponente intern mit Windows-Messages arbeitet, sehe ich aber auch keine Alternative. Mit Deinem Thread hebelst Du ja nur solche Dinge wie eben ein Sleep aus. Die einzige Lösung wäre tatsächlich eine MessageProc innerhalb des Threads, die nur für diese Message da ist. Ob ein Thread eine eigene Message-Queue besitzen darf, weiß ich aber nicht.
Zitat:

Zitat von DocE
Kannst Du mir ein Beispiel geben, wann das z.B. problematisch werden kann. Ich konnte das bisher nämlich nicht nachvollziehen, dass es zu Problemen kommt.

Schwierig, da solche Fehler schwer zu reproduzieren sind (Stchwort Mandelbugs). Das Problem ist aber das Selbe wie der Aufruf einer VCL-Methode aus einem Thread heraus, ohne Synchronize zu verwenden. Du weißt ja nicht, in welchem Zustand sich der Hauptthread befindent, wenn Du die Nachrichten abarbeitest.

Gruß
xaromz

DocE 6. Okt 2006 15:49

Re: Events in TThread
 
Hallo,

ich habe jetzt nochmal ein bißchen rumprobiert und vertrete weiterhin die Meinung, dass der Thread nicht die Messages des Hauptthreads "bekommt".

Dazu habe ich die MessageQueue des Hauptthreads "aufgefüllt" und mit sleep(x) die Verarbeitung angehalten, während im TThread weiter Application.ProcessMessages ausgeführt wurde. Nach dem Sleep habe ich die Messages in ein Memo schreiben lassen. Sie waren immer noch da.

Dann habe ich die Messages des TThreads und vom Hauptthread in Memos schreiben lassen (zeitgleich) und siehe da, es waren unterschiedliche. Der TThread bekam ausschließlich MM_WIM_OPEN (1x) und MM_WIM_DATA (wiederkehrend) als Message. Der Hauptthread allerdings trotz sleep, alle möglichen. Das bedeutet, dass der TThread die Messages des Hauptthreads weder empfangen, noch abgearbeitet hat.

Das Application.ProcessMessages macht im Prinzip (vereinfacht) nichts anderes als:

Delphi-Quellcode:
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;
In der Hilfe ist zu finden:

Delphi-Quellcode:
BOOL PeekMessage(

    LPMSG lpMsg,   // pointer to structure for message
    HWND hWnd,   // handle to window
    UINT wMsgFilterMin,   // first message
    UINT wMsgFilterMax,   // last message
    UINT wRemoveMsg    // removal flags
   );
Merke: Es wird also als HWND Null übergeben.

Unter Remarks:

Zitat:

PeekMessage retrieves only messages associated with the window identified by the hWnd parameter or any of its children as specified by the IsChild function, and within the range of message values given by the wMsgFilterMin and wMsgFilterMax parameters.
Wäre ja nicht so gut, aber weiter:

Zitat:

If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread making the call. (PeekMessage does not retrieve messages for windows that belong to other threads.)
Nach meinem Verständnis werden bei Application.ProcessMessages ausschließlich Messages abgehandelt, die zu dem aktuellen Thread gehören, was meine Tests bestätigen würden.

Oder seht ihr einen Denkfehler?


Grüsse
...Doc

pertzschc 6. Okt 2006 16:09

Re: Events in TThread
 
Hallo DocE,

mag sein, dass ich falsch liege - ich würde Dir raten, einen Thread A zu erstellen, der intern die Audiokomponente hält (Create & Free zu den entsprechenden Zeitpunkten).

Parallel dazu einen Thread B erstellen, der eine Behandlungsmethode für das Event der Audiokomponente enthält.
Diese Behandlungsmethode würde ich auf das Event der Komponente in Thread A zuweisen.

Thread A kann dann Warterunden in seiner Execute-Methode drehen und Thread B ebenso.
Allerdings wird in Thread B dann die Execute-Methode von der Ausführung der Behandlungsmethode unterbrochen.

Um in Deiner HauptVCL-Anwendung Dinge zu verändern, benötigst einen Synchronized-Aufruf in Dein Form.
Diesen würde ich in Thread A in einer Methode implementieren und diese Methode durch einen Event von
Thread B aus auslösen.

Gruß,
Christoph

DocE 6. Okt 2006 16:26

Re: Events in TThread
 
Hallo Christoph,

die Audioauswertung in zwei Threads auszulagern sehe ich nicht als besonder sinnvoll an. Es funktioniert ja in einem Thread bereits wunderbar. Das Problem war ja nur, dass der Thread in einer Schleife Application.ProcessMessages ausführen muss, damit das Ereignis überhaupt ausgelöst wird. Das müßte einer Deiner beiden Threads ja trotzdem machen, sonst würde er das Ereignis nicht bekommen oder "auslaufen" (-> Terminated).

Momentan stellt sich eigentlich nur noch folgende Frage:

In meinem letzten Post habe ich folgende These aufgestellt und begründet:

Zitat:

Application.ProcessMessages verarbeitet ausschließlich Messages aus dem aufrufenden Thread und ist somit Thread-sicher.
Wenn dies so wirklich der Fall ist und das ist es meiner Meinung nach (Begründung siehe HIER), dann kann dieses problemlos in einer Schleife eines TThreads ausgeführt werden und alles ist gut.

Die Frage ist jetzt nur, ob jemand meine begründete These widerlegen kann.


Grüsse
...Doc

SirThornberry 6. Okt 2006 18:09

Re: Events in TThread
 
Application.Processmessages ruft Prozessmessages in einer Schleife auf. ProcessMessages setzt wiederum eine private-Variable von TApplication. Das setzen dieser Variablen ist nicht mit CriticalSections abgesichert so das es dort krachen könnte. Allerdings hat dies nichts mit dem berichteten Fehler zu tun (Leinwandbild erlaubt....). Du greifst definitiv irgendwo auf grafische Elemente des Hautpthreads zu.

DocE 6. Okt 2006 18:31

Re: Events in TThread
 
N'Abend SirThornberry,

das stimmt. Es gibt evtl. sogar noch ein paar weitere Punkte an denen es zu Komplikationen kommen könnte z.B. hier, falls irgendein Ereignis zugewiesen ist.

Delphi-Quellcode:
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
Aber man könnte sich ja das ProcessMessages für den TThread einfach selbst schreiben durch:

(verkürzt)

Delphi-Quellcode:
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin

  TranslateMessage(Msg);
  DispatchMessage(Msg);

end;
oder

(fast 1:1)

Delphi-Quellcode:
procedure MyProcessMessageImThread(var Msg: TMsg);
begin

  while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
  begin
   
    if Msg.Message <> WM_QUIT then // bekommt ein Thread vermutlich nie, aber gut
    begin

      // trifft alles für Threads normalerweise nicht zu
      {if not IsHintMsg(Msg) and not IsMDIMsg(Msg) and
       not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then} 

        TranslateMessage(Msg);
        DispatchMessage(Msg);

    end;
 
  end;

end;
Ich denke, dann sollte es zu keinem Problem kommen.

Irgendwie kursiert wohl allgemein das Gerücht, dass ein TThread mit Application.ProcessMessages auch auf die Messages des Hauptthreads zugreift. Dies ist jedoch nicht richtig! Jeder Thread hat praktisch seine eigene MessageQueue. Mit diesem Vorurteil sollte einmal aufgeräumt werden.


Grüsse
...Doc


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:04 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz