![]() |
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 |
Re: Events in TThread
Ein Zauberwort heißt "Synchronize" (OH). Damit sollten sich einige Probleme erübrigen.
|
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 |
Re: Events in TThread
Zitat:
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. |
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 |
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:
In dieser Schleife muss irgendetwas anderes passieren (anstatt Application.ProcessMessages) damit der Thread seine "eigenen" Events, hier: OnBufferFilled der AudioKomponente, bearbeiten kann.
while not Terminated do
begin sleep(100); Application.ProcessMessages; end; Ohne Application.ProcessMessages passiert gar nichts (keine Bearbeitung des Events). Grüsse ...Doc |
Re: Events in TThread
Zitat:
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. |
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... |
Re: Events in TThread
Zitat:
|
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:
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.
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; Wenn hingegen die Audiokomponente asynchron läuft, dann hast Du mit einem Thread ein Problem. Aber dann benötigst Du auch keinen. Gruß xaromz |
Re: Events in TThread
So, zunächst zu :
Zitat:
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:
Dieses wurde ursprünglich erzeugt mit:
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;
Delphi-Quellcode:
Da ein Thread keinen TWinControl-Owner hat, habe ich das ganze abgewandelt in:
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 );
Delphi-Quellcode:
Die WndProc war einfach leer. 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 ); 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 |
Re: Events in TThread
Hallo,
Zitat:
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:
2: Wie gesagt, ich würde nur die reine Verarbeitung in einen Thread auslagern, dann stellt sich diese Frage nicht mehr. Gruß xaromz |
Re: Events in TThread
Mahlzeit,
Zitat:
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:
Grüsse ...Doc |
Re: Events in TThread
Hallo,
Zitat:
Zitat:
Gruß xaromz |
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:
In der Hilfe ist zu finden:
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin TranslateMessage(Msg); DispatchMessage(Msg); end;
Delphi-Quellcode:
Merke: Es wird also als HWND Null übergeben.
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 ); Unter Remarks: Zitat:
Zitat:
Oder seht ihr einen Denkfehler? Grüsse ...Doc |
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 |
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:
![]() Die Frage ist jetzt nur, ob jemand meine begründete These widerlegen kann. Grüsse ...Doc |
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.
|
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:
Aber man könnte sich ja das ProcessMessages für den TThread einfach selbst schreiben durch:
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
(verkürzt)
Delphi-Quellcode:
oder
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin TranslateMessage(Msg); DispatchMessage(Msg); end; (fast 1:1)
Delphi-Quellcode:
Ich denke, dann sollte es zu keinem Problem kommen.
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; 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