![]() |
Synchronize und die Messagequeue
Hallo,
ich habe ein kleines Problem mit den Methoden "TThread.Synchronize" bzw. "TThread.Queue". Ich verwende diese Methoden um Events aus meinem Audiosystem mit dem Hauptthread der VCL zu synchronisieren. Intern fügt "Synchronize" einen Record mit den Informationen über die übergebene Funktion zu einer Liste hinzu und sendet dann via "PostMessage" die Message WM_NULL an den Hauptthread. Wird dort WM_NULL empfangen so wird die Liste der zu synchronisierenden Methoden abgearbeitet. Soweit die Theorie. Nun ergibt sich aber folgendes Problem: Scheinbar arbeitet meine Anwendung nur dann WM_NULL ab, wenn eine andere Message eintrifft - zum Beispiel wenn die Maus bewegt wird oder ein Timer-Event ausgelöst wird. Hierdurch stauen sich in meinem eigenen Synchronisierungsthread die "Anträge". Erst wenn man die Maus eine Weile über einem Fenster der Anwendung bewegt, werden die Events nach und nach abgearbeitet. Hier ein kurzer Codeauszug:
Delphi-Quellcode:
Den ganzen Code gibt es hier:
procedure TAuSyncMgr.Execute;
begin try while not Terminated do begin while true do begin FCurMem := nil; FDeletedCurMem := false; FCritSect.Enter; try if FCallList.Count > 0 then FCurMem := PThreadMethod(FCallList[0]); finally FCritSect.Leave; end; if FCurMem = nil then break; try try {$IFNDEF DO_NOT_USE_VCL} //Erst nach dem Abarbeiten von "FCurMem^" durch Synchronize kehrt die Funktion zurück. //Das ist auch gut so. Jedoch wird FCurMem^ erst nach dem Eintreffen einer Windowmessage //wie "WM_MOUSEMOVE" abgearbeitet. Das ist nicht so schön. Synchronize(FCurMem^); {$ELSE} FCurMem^; {$ENDIF} finally if not FDeletedCurMem then begin //Remove the element from the list FCritSect.Enter; try FCallList.Delete(0); //Free the memory reserved for the method pointer FreeMem(FCurMem, SizeOf(TThreadMethod)); finally FCritSect.Leave; end; end; end; except // end; end; Sleep(1); end; except // end; end; ![]() Hat jemand eine mögliche Lösung für das Problem? Das Ganze sollte keine Änderung am Code des Endanwenders (sprich Formulare etc.) benötigen, sondern sich auf meinen Bibliothekscode beschränken. Einen Timer zu Formular hinzuzufügen (was übrigens zu langsam ist, da ich ca. 50-100 Events pro Sekunde auf diese Weise synchronisiere), ist also keine Lösung. Ich hoffe mir kann hier jemand helfen. Schon einmal Danke, Andreas |
Re: Synchronize und die Messagequeue
Eventuell wird WM_NULL (ob von der VCL oder Windows oder vom Weihnachstmann) etwas anders behandelt. Als aller ersten Versuch würde ich eine eigene WM_USER + X Message mal ausprobieren statt WM_NULL. Das wäre ohnehin sauberer, da WM_NULL durchaus einen Sinn hat: Nämlich den, dass man davon ausgehen können soll, dass eine Anwendung ausser der Abarbeitung der Message keine anderen Operationen als Reaktion auf die Message ausführt. Ein Scheduler könnte damit also z.B. prüfen ob ein Programm nicht abgestürzt ist indem es in Pulsen WM_NULL broadcastet, was bei dir dann auf einmal echte Programmlogik nach sich zöge. (Ich weiss nicht ob Windows das nicht auch in echt so macht, aber es wäre eine gute Möglichkeit.)
|
Re: Synchronize und die Messagequeue
Hallo,
erstmal Danke für die Antwort. In der Tat erreicht die Message "WM_NULL" niemals die Messagequeue des TApplication-Objekts. Dort steht:
Delphi-Quellcode:
Diese Zeile wird nie Aufgerufen!
WM_NULL:
CheckSynchronize; Zusätzlich wird "CheckSynchronize" nach jeder verarbeiteten Message (Also in Application.Idle) aufgerufen - das ist das, was ich durch bewegen der Maus auslöse. Ersetzen der Zeile
Delphi-Quellcode:
Durch
procedure TApplication.WakeMainThread(Sender: TObject);
begin PostMessage(Handle, WM_NULL, 0, 0); end;
Delphi-Quellcode:
bringt leider keine Besserung. Die Message wird gar nicht empfangen. Sende ich die Message jedoch an Form1.Handle und nicht TApplication.Handle, so funktioniert das Ganze. Hat jemand eine Erklärung für dieses Verhalten?
const
WM_SYNCHRONIZE_THREADS = WM_USER + 1187; PostMessage(Handle, WM_SYNCHRONIZE_THREADS, 0, 0); |
Re: Synchronize und die Messagequeue
Das könnte evtl. daran liegen, dass das Application-Formular nicht sichtbar ist. Ich kann es nicht mit Bestimmtheit sagen, aber ich kann mir vorstellen, dass dann entweder manche Messages garnicht verschickt, oder aber nicht verarbeitet werden - entweder auf Seiten des OS, oder der VCL. Ich hatte erst an die Unterscheidung von "WM_" Messages zu "CM_" Messages gedacht, aber es ist ja dann auch wieder WM_MOUSE_MOVE wenn ich nicht irre. :gruebel:
Wenn du den Source hast, wäre es vermutlich hilfreich mal in die Message-Loop von TApplication zu schauen. Vielleicht ist da ja schon was erkennbar, dass irgendwo unterschieden wird. Das ist jetzt aber auch nur ein Versuch einer Idee, ich hab hier leider kein Delphi zum genau nachschauen. |
Re: Synchronize und die Messagequeue
Zitat:
lg. Astat |
Re: Synchronize und die Messagequeue
Zitat:
Lasse ich die Message jedoch an mein Formular schicken und fange sie dort ab, dann geht es. Das mit der Sichtbarkeit wäre eine Erklärung - die Frage ist dann jedoch, warum die Leute von (damals) Borland das so implementiert haben. Schließlich war TApplication auch schon bei Delphi 1 unsichtbar. Die Synchronisierung hat also noch nie richtig funktioniert. |
Re: Synchronize und die Messagequeue
Zitat:
Delphi-Quellcode:
in der Luft hängt!?
PThreadMethod(FCallList[0]);
Astat |
Re: Synchronize und die Messagequeue
Delphi-Quellcode:
bei mir lösen beide Varianten
PostMessage(Application.Handle, WM_NULL, 0, 0);
und SendMessage(Application.Handle, WM_NULL, 0, 0);
Delphi-Quellcode:
aus
WM_NULL:
CheckSynchronize; Application.Handle muß schon sein, denn immerhin steckt dieses in der WindowProc des Application-Fensters. |
Re: Synchronize und die Messagequeue
Also die Sache über WM_Null funktioniert. Es wird zigfach eingesetzt, da es ja der Code von Delphi ist und den kaum einer ändern wird.
Das Synchronize Probleme macht und mit Bedacht eingesetzt werden muss ist klar:
|
Re: Synchronize und die Messagequeue
Der Fehler scheint irgendwie in meinem Projekt zu liegen.
Erstelle ich eine leere VCL-Formularanwendung und schreibe
Delphi-Quellcode:
in OnFormCreate, so wird WM_NULL ordnungsgemäß empfangen.
PostMessage(Application.Handle, WM_NULL, 0, 0);
Füge ich denselben Code in die Betroffene Anwendung, an die selbe Stelle, so ist dies nicht der Fall. Ich muss da wohl mal ein wenig suchen... |
Re: Synchronize und die Messagequeue
So, ich habe den "Fehler" gefunden:
Meine Anwendung hat ein Konsolenfenster zur Debugausgabe. Dieses habe ich mittels
Delphi-Quellcode:
erstellt. Lasse ich das Konsolenfenster weg, so geht es.
{$APPTYPE CONSOLE}
Irgendeinen Weg die Applikation trotzdem am Laufen zu halten? Workarounds? Vorschläge? Danke an alle bisher Antwortenden, Andreas |
Re: Synchronize und die Messagequeue
TApplication und 'Consolenanwendung' schließen sich imho aus. Verwende etwas anderes zur Log-Ausgabe, z.b. ein einfaches Fenster mit einer MEMO, an die Du per Message deine Logausgaben schickst.
|
Re: Synchronize und die Messagequeue
Zitat:
Naja, wenn es nicht anders geht... |
Re: Synchronize und die Messagequeue
Statt {$APPTYPE CONSOLE} einfach
![]() z.B.:
Delphi-Quellcode:
UseConsole := AttachConsole(ATTACH_PARENT_PROCESS) or AllocConsole;
If UseConsole Then ConsoleHandle := GetStdHandle(STD_OUTPUT_HANDLE); If UseConsole Then WriteLn(ConsoleHandle, 'Debugausgabe'); kleine Erklärung zu einigen Änderungen: * TAuSyncMgr.Create - der Vorfahre wird besser zuerst initialisiert * leere Try-Except-Blöcke sind eigentlich nicht so schön * mgr kann nie NIL sein, denn diese Unit, bzw. deren finalization wird erst aufgerufen, wenn die Unit nirgendwo mehr benötigt wird ... drum kann nichts mehr danach (nach mgr.Free) etwas hiervon aufrufen (es sei denn jemand umgeht diese Hierarchie und gibt einen Pointer auf eine deiner beiden Prozeduren an eine Stelle, wo mgr doch schon freigegeben wurde, aber dann ist er für die darauffolgende Exception selber Schuld) * das N in ifNdef übersieht man schnell mal
Delphi-Quellcode:
{*******************************************************}
{ } { Audorra Digital Audio Library } { Copyright (c) Andreas St�ckel, 2009 } { Audorra is an "Andorra Suite" Project } { } {*******************************************************} {The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at [url]http://www.mozilla.org/MPL/[/url] Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Initial Developer of the Original Code is Andreas St�ckel. All Rights Reserved. Alternatively, the contents of this file may be used under the terms of the GNU General Public License license (the �GPL License�), in which case the provisions of GPL License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL License and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL License. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the GPL License. File: AuSyncUtils.pas Author: Andreas St�ckel } {AuSyncUtils allows threads to send messages to each other.} unit AuSyncUtils; interface {$IFDEF FPC} {$MODE DELPHI} {$ENDIF} {$I commons_conf.inc} uses SysUtils, Classes, AcSyncObjs, AcPersistent, AcSysUtils; {Adds a method to the queue. It is unknown wheter or when the method will be executed. Do not use this function for important messages, but only for notifying. If you're working in a console/non VCL-Application, you have to declare the NO_VCL compiler switch.} procedure AuQueueCall(AProc: TThreadMethod); {Removes an object from the queue - this prozedure should be called, if a method, which is able to have calls on the queue, is freed.} procedure AuQueueRemove(AObj: Pointer); implementation type PThreadMethod = ^TThreadMethod; PMethod = ^TMethod; TAuSyncMgr = class(TThread) private FCallList: TList; FCritSect: TAcCriticalSection; protected procedure Execute; override; public procedure QueueCall(AProc: TThreadMethod); procedure DeleteObject(AObj: Pointer); constructor Create; destructor Destroy; override; end; constructor TAuSyncMgr.Create; begin inherited Create(False); FCallList := TList.Create; FCritSect := TAcCriticalSection.Create; end; procedure TAuSyncMgr.DeleteObject(AObj: Pointer); var i: Integer; mem: PMethod; begin FCritSect.Enter; try i := FCallList.Count - 1; while i >= 0 do begin mem := PMethod(FCallList[i]); if mem^.data = AObj then begin FreeMem(FCallList[i]); FCallList.Delete(i); end; Dec(i); end; finally FCritSect.Leave; end; end; destructor TAuSyncMgr.Destroy; var i: integer; begin for i := FCallList.Count - 1 downto 0 do FreeMem(FCallList[i]); FCallList.Free; FCritSect.Free; inherited; end; procedure TAuSyncMgr.Execute; var CurMem: TThreadMethod; begin while not Terminated do begin FCritSect.Enter; try if FCallList.Count > 0 then begin CurMem := PThreadMethod(FCallList[0])^; FreeMem(FCallList[0]); FCallList.Delete(0); end else CurMem := nil; finally FCritSect.Leave; end; if Assigned(CurMem) then try {$IFDEF DO_NOT_USE_VCL} CurMem; {$ELSE} Synchronize(CurMem); {$ENDIF} except // end; Sleep(1); end; end; procedure TAuSyncMgr.QueueCall(AProc: TThreadMethod); var mem: PThreadMethod; begin FCritSect.Enter; try GetMem(mem, SizeOf(TThreadMethod)); mem^ := AProc; FCallList.Add(mem); finally FCritSect.Leave; end; end; var mgr: TAuSyncMgr; procedure AuQueueCall(AProc: TThreadMethod); begin mgr.QueueCall(AProc); end; procedure AuQueueRemove(AObj: Pointer); begin mgr.DeleteObject(AObj); end; initialization mgr := TAuSyncMgr.Create; finalization mgr.Terminate; mgr.WaitFor; mgr.Free; end. |
Re: Synchronize und die Messagequeue
Hallo,
Zitat:
Zitat:
Zitat:
Zitat:
Trotzdem vielen Dank fürs durchschauen des Codes, besonders das mit den Try-Except-Blöcken übernehme ich. Der Code hat jetzt auch schon mehr als ein Jahr lang "Reifung" hinter sich, also nicht wundern... |
Re: Synchronize und die Messagequeue
Zitat:
Zitat:
Ich hab es einmal gemacht und versucht alle möglichen Fehler abzufangen ... ist jetzt vielleich schon 4-5 Jahre her und am Ende waren über 80% des Codes nur noch Fehlerbehandlung und dabei war noch nichtmal alles behandelt. :shock: OK, ein FreeAndNil erzeugt wenigstens noch eine aussagekräftge Fehlermeldung, wenn/falls doch nochmal mgr genutzt würde.
Delphi-Quellcode:
finalization
mgr.Terminate; mgr.WaitFor; FreeAndNil(mgr); end. |
Re: Synchronize und die Messagequeue
Zitat:
Zitat:
Delphi-Quellcode:
Weiterhin würde ich den Thread nicht ständig im Kreis rennen lassen, verwende doch einfach eine Semaphore oder ein Event. im Execute wartest Du auf das Event (WaitForSingleObject), im AuQueueCall zuppelst Du am Event (SetEvent).
constructor TAuSyncMgr.Create;
begin inherited Create(True); // <--- Der Parameter heißt 'CreateSuspended' FCallList := TList.Create; FCritSect := TAcCriticalSection.Create; Resume; // <--- Aufwecken, geht auch später end; |
Re: Synchronize und die Messagequeue
Der Thread wird erst dort gestartet, also nach dem Constructor.
Delphi-Quellcode:
Aber notfalls/sicherheitshalber ginge auch alzaimars Variante.
procedure TThread.AfterConstruction;
begin if not FCreateSuspended and not FExternalThread then Resume; end; |
Re: Synchronize und die Messagequeue
Resume und Suspend werden unter Linux nicht unterstützt und sollten (aus gutem Grund) auch nicht verwendet werden. ;-)
Aber sonst habt ihr natürlich recht. Mir war fast klar, dass der Thread erst in "AfterConstruction" erzeugt wird, wie gesagt, ich war nur zu Faul nachzuschauen. Und der Speicher für das Objekt wird ja schon in "BeforeConstruction" reserviert - also sehe ich mit meiner Methode kein Problem. Mit Events habe ich noch nie gearbeitet, aber intern sollten die doch auch nicht viel anders arbeiten, oder? Wäre ich damit endlich mal vom Scheduler losgelöst, der mir nach dem Aufruf von "Sleep" erst nach ca. 20ms wieder die Kontrolle über mein Programm zurückgibt, dann würde ich es sofort verwenden. |
Re: Synchronize und die Messagequeue
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:31 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-2025 by Thomas Breitkreuz