![]() |
Delphi-Version: 5
Object mit SendMessage an MainThread senden
Hi,
ich möchte ein Object aus meinem Thread an den Hauptthread senden. So hab ich mir das vorgestellt. (TLogMessage ist hier nur ein Beispiel, was ich zum Testen genommen habe). Verschicken:
Delphi-Quellcode:
Empfangen im Hauptthread:
var
LogMsg: TLogMessage; const PM_MsgFromThread = WM_USER +4; procedure TComThread.SendMsgToMain(LogText: string; Debug: Boolean); begin LogMsg:=TLogMessage.Create; LogMsg.sLogText:=LogText; LogMsg.bIsDebug:=Debug; SendMessage(FMainFormHandle, PM_MsgFromThread, Integer(@LogMsg), 0); end;
Delphi-Quellcode:
Ich befürchte aber, dass ich es mal komplett falsch mache.
procedure Tfrm_main.LogMsgFromThread(var msg: TMessage);
var P: TLogMessage; fdebug: Boolean; fLogText: string; begin P:=Pointer(msg.WParam); fdebug:=P.bIsDebug; fLogText:=P.sLogText; //P.Free; //Ich müßte das Object auch wieder freigeben. end; Auf P.bIsDebug kann ich noch zugreifen, und bei P.sLogText knallt es mit einer Zugriffsverletzung. |
AW: Object mit SendMessage an MainThread senden
Integer ist nicht gut, stell dir mal vor man verwendet den Code mal im Win64.
NativeInt, oder für Pointer besser IntPtr, oder im Fall von SendMessage/PostMessage den OriginalTyp LRESULT.
Delphi-Quellcode:
function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
Was mag wohl passieren, wenn mal zwei Threads zugleich diese Variable nutzen wollen? Objekte sind schon Pointer und lassen sich problemlos in "Integer" konvertieren, aber egal wie, man muß das schon richtig machen. :stupid:
Delphi-Quellcode:
Also warum ist LogMsg eine globale Variable, und warum gibt es die überhaupt? :stupid:
LPARAM(@LogMsg) => PPointer(msg.LParam)^
LPARAM(LogMsg) => PPointer(msg.LParam) // ohne externe Variable Und ja, das müsstest du. (Free) |
AW: Object mit SendMessage an MainThread senden
Zitat:
Aber ich hab jetzt meinen Fehler gefunden, lag in der Deklaration von P
Delphi-Quellcode:
So funktioniert's zwar, aber beim Free hab ich immer noch eine Exception.
P: ^TLogMessage;
Das mit den Int64 macht natürlich Sinn. Also so:
Delphi-Quellcode:
?
SendMessage(FMainFormHandle, PM_MsgFromThread, Intptr(@LogMsg), 0);
Leider hab ich Probleme, deine durchaus sinnvollen und hilfreichen Anmeldungen in meinen oben beschriebenen Code umzusetzen. Weil ich tatsächlich nicht weiß, ob ich das jetzt richtig interpretiert habe. Problematisch ist halt immer, wenn es oft mehrere Möglichkeiten gibt, genau das gleiche zu machen. Und wenn man dann in dem Thema nicht fit ist, dann würfelt man (in dem Fall ich) halt schnell was durcheinander. |
AW: Object mit SendMessage an MainThread senden
müsste eigenlich sein: (ohne @)
Delphi-Quellcode:
Weil ein Object ist schon selbst ein Pointer.
Integer(LogMsg)
Ein TObject auf Integer zu casten hat eigentlich immer funktioniert. |
AW: Object mit SendMessage an MainThread senden
Dann bekomme ich wieder beim zugriff auf sLogText eine Zugriffsverletzung.
Ich hab's jetzt so: Senden:
Delphi-Quellcode:
Empfangen:
procedure TComThread.SendMsgToMain(LogText: string; Debug: Boolean);
begin LogMsg:=TLogMessage.Create; LogMsg.sLogText:=LogText; LogMsg.bIsDebug:=Debug; SendMessage(FMainFormHandle, PM_MsgFromThread, Intptr(@LogMsg), 0); end;
Delphi-Quellcode:
Funktioniert auch gut. Aber ich müßte das Object auch mal wieder Freigeben. Aber das Problem ist, ich weißt nicht wirklich wie?
var
f: TextFile; fname: string; P: ^TLogMessage; fdebug: Boolean; fLogText: string; begin P:=Pointer(msg.WParam); fdebug:=P.bIsDebug; fLogText:=P.sLogText; //TLogMessage(p).free; end; So wie oben? oder p.free? |
AW: Object mit SendMessage an MainThread senden
Du versendest die Adresse einer lokalen Variablen (@LogMsg) in der die Adresse des Objektes gespeichert ist.
Sinnvoller ist es natürlich die Adresse des Objektes zu versenden.
Delphi-Quellcode:
Vermutlich ist dir nicht klar, das diese Nachricht mit SendMessage noch innerhalb des Absender-Threads ausgeführt wird!
SendMessage(FMainFormHandle, PM_MsgFromThread, Integer(Pointer(LogMsg)), 0);
{...} var P: TLogMessage; begin P := Pointer(msg.WParam); {...} end; Wenn das Objekt beim Empfänger nur wärend der Verarbeitung der Nachricht benötigt wird, sollte es beim Absender danach wieder freigegeben werden. Es ist ja nicht sichergestellt, dass der Empfänger die Nachricht auch in jedem Fall erhält.
Delphi-Quellcode:
Im Gegensatz dazu wird die Nachricht mit Postmessage erst einmal in die Nachrichtenschlange des Fensters geschoben und dort vieleicht irgendwann vom Hauptthread abgeholt.
LogMsg:=TLogMessage.Create;
try SendMessage(FMainFormHandle, PM_MsgFromThread, Integer(Pointer(LogMsg)), 0); finally LogMsg.Free; end; Hier muss der Empfänger die Freigabe übernehmen. Allerdings ist auch hier nicht sichergestellt, dass die Nachricht den Empfänger erreicht. Dann würde ein Speicherleck entstehen. Für diesen Fall ist es möglich ein eigene Verwaltung der Nachrichtenobjekte anzulegen (z.B. eine Objektliste auf die aus beiden Threads nur über eine CriticalSection zugegriffen wird). Der Mainthread wird als Observer per Postmessage benachrichtigt, dass neue Nachrichtenobjekte eingegangen sind, muss diese aber selbst abholen. Nicht verarbeitete Nachrichtenobjekte können so auch z.B. beim Beenden des Threads oder spätestens bei der Freigabe der Nachrichtenverwaltung freigegeben werden. |
AW: Object mit SendMessage an MainThread senden
jetzt übergibst Du einen Pointer von einem Pointer .. also doppelt gemoppelt.
So wie ich das geschrieben habe, geht es auf jeden Fall. Ich habe für sowas sogar extra eine Klasse gebaut, die (urpsrünglich) normale Events als asynchrone Events genau nach dieser Methode vom Thread an den Hauptthread schickt. Umwandlung dann natürlich: ... nicht Pointer() sondern TLogMessage()
Delphi-Quellcode:
var
logMessage: TLogMessage; fdebug: Boolean; fLogText: string; begin logMessage := TLogMessage(msg.WParam); mit dem doppelt gemoppelten Pointer in Deiner jetzigen Version wäre das dann:
Delphi-Quellcode:
logMessage := TLogMessage(msg.WParam^);
|
AW: Object mit SendMessage an MainThread senden
Blup hat recht und ist die beste Lösung,
Du läufst Gefahr, wenn du z.B. den Text direkt in einem Label ausgeben möchtest, dass du deinen Thread mit der VCL synchronisieren musst und je nach Menge der Messages deinen Thread damit auch ausbremst. Generier eine
Delphi-Quellcode:
und schließe einen Gegenseitigen Zugriff auf die Liste mit den Critical Sections aus.
ObjectList<TLogMessage>
Du brauchst dann auch nur den Index der neuesten Nachricht an den Empfänger senden anstatt des Objects oder eines Pointers auf das Object. |
AW: Object mit SendMessage an MainThread senden
Zitat:
ansonsten ist der überlieferte Index nicht mehr korrekt. Grüße Klaus |
AW: Object mit SendMessage an MainThread senden
Stimmt!
Eigentlich sollte der Index eh irrelevant sein, da der Empfänger alle Nachrichten nacheinander abarbeitet (arbeiten sollte) und dann entfernt. |
AW: Object mit SendMessage an MainThread senden
Zitat:
Muss nix synchronisiert werden. Postmessage geht übers Betriebssystem und kommt im Hauptthread der Anwendung wieder an und der Text kann dort in einem Label angezeigt werden. Man spart sich den Aufruf von synchronize, der WorkerThread wird demnach auch nicht für einen kurzen Moment angehalten. Bei hochperformanten Anwendungen sollte man dies so lösen. |
AW: Object mit SendMessage an MainThread senden
Zitat:
|
AW: Object mit SendMessage an MainThread senden
Und wozu das mit dem Rüberreichen über einen Pointer und woanders freigeben?
Geht das nicht noch etwas komplizierter? Warum kein ![]() Es liest sich sehr unausgegoren und fehleranfällig an. |
AW: Object mit SendMessage an MainThread senden
Queue ist komfortabler aber erst in neueren Delphiversion möglich. Im Prinzip arbeitet es genauso wie Postmessage.
Das Problem Objekte sicher von einem Thread an einen anderen weiter zu reichen, wird dadurch allein aber nicht gelöst. |
AW: Object mit SendMessage an MainThread senden
Zitat:
|
AW: Object mit SendMessage an MainThread senden
Captnemo hat am Anfang angegeben er verwendet D5 ;-)
|
AW: Object mit SendMessage an MainThread senden
Zitat:
Aber wie ich vorher shcon sagte: Wenn man die globale Variable referenziert (@) und darauf den Zeiger überbergibt, dann muß am Ziel auch dieser Zeiger erstmal dereferenziert (^) werden, und wenn man direkt den Objektzeiger übergibt, dann muß nur gecastet werden. Und da Pointer was Böses sind, sollte man Diese möglicht vermeiden und schon gibt es keine Probleme mehr. :mrgreen: |
AW: Object mit SendMessage an MainThread senden
Warum TLogMessage als Object?
Delphi-Quellcode:
Empfangen im Hauptthread:
type
PLogMessage = ^TLogMessageMSG; TLogMessageMSG = record sLogText : String[255]; bIsDebug : boolean; end; var LogMsg: PLogMessage; const PM_MsgFromThread = WM_USER +4; procedure TComThread.SendMsgToMain(LogText: string; Debug: Boolean); begin New(LogMsg); LogMsg^.sLogText:=LogText; LogMsg^.bIsDebug:=Debug; SendMessage(frm_Main.Handle, PM_MsgFromThread,0,LParam(LogMsg)); end;
Delphi-Quellcode:
Mavarik
procedure Tfrm_main.LogMsgFromThread(var msg: TMessage);
var P: PLogMessage; fdebug: Boolean; fLogText: string; begin try P := PLogMessage(MSG.LParam); fdebug:=P.bIsDebug; fLogText:=P.sLogText; finally Dispose(P); end; end; |
AW: Object mit SendMessage an MainThread senden
Ob Object oder Record-Pointer, ist fast egal. (der kleiner Overhead des Objects fällt hier praktisch garnicht auf)
Aber wenn schon globale Variable, warum dann einen Record-Pointer? Der Record direkt als Variable und keiner muß den mehr freigeben und auch vorher nicht erstellen. PS: Wenn es die globale Variable gibt, dann fällt eigentlich sofort auf, daß man sich hier den Parameter sparen kann, da man auch gleich auf die Variable zugreifen kann. :stupid: Und schon fällt noch besser auf, warum diese Variable besser vernichtet gehört. |
AW: Object mit SendMessage an MainThread senden
Eine Liste mit durch Critical Sections gesichertem Zugriff ist imho fast die einzige vernünftige Art Daten unter verschiedenen Threads auszutauschen. Dem Formular dann nur noch per PostMessage() parameterlos mitteilen, dass es da neues drin gibt, und dieses arbeitet immer schon Index 0 ab und löscht aus der Liste bis sie leer ist. FiFo-Style.
Am Rande: SendMessage() ist in Threads, wenn ich mich nicht grad irre, eigentlich sogar ein ziemliches NoGo, da alles was die Empfänger dann tun auf ein Mal im Thread-Kontext statt findet. Und das kann je nach dem auch schon mal so richtig böse knallen, da es OS Ressourcen gibt, die nur im erstellenden Kontext verwendet werden dürfen. Davon ab pausiert der Thread bis alle Handler von den Empfängern fertig sind, und das ist ja nun auch nicht ganz der Sinn von Parallelisierung. |
AW: Object mit SendMessage an MainThread senden
So hab ich es jetzt letztlich im Moment auch gelöst. Im MainTread eine Tobjectlist, vom Thread innerhalb ein TCriticalSection Daten hinzu, und per postmessage benachrichtigen.
Aber ich will mir auch nochmal die TThread.Queue anschauen, denn das find auch ganz interessant. |
AW: Object mit SendMessage an MainThread senden
Nein, SendMessage/PostMessage ist in Threads kein Problem.
Beides trägt die Message in den MessageQueue des Threads ein, in welchem die Komponente erstellt wurde, an die die Message geht. Verarbeitet wird die Message dann immer von der Messagebehandlung im zugehörigen Thread (meist der Hauptthread). TThread.Queue ist wie Synchronize, außer daß die Prozedur nicht sofort verarbeitet wird. (von dem "Bug" abgesehn, wenn man was vom MainThread aus daran übergibt) |
AW: Object mit SendMessage an MainThread senden
Zitat:
Zitat:
Zitat:
PostMessage, SendMessage usw geht in de Queue vom jeweiligem Thread. Überprüfen kannst Du, von welchem Thread die Funktion aufgerufen wird mit: GetCurrentThreadID Wenn ein eigener Thread wirklich seine Nachrichten auch behandeln will, sofern er überhaupt welche bekommt, dann mit PeekMessage, TranslateMessage in der Execute Schleife von TThread. |
AW: Object mit SendMessage an MainThread senden
Danke für die Aufklärung! War bisher zu faul das genauer zu testen. Das Problem was ich mal hatte wodurch ich dazu kam, war gelöst als ich noch eine ganze Menge mehr umgebaut habe. Das war dann also schon mal nicht der Suppenspucker.
|
AW: Object mit SendMessage an MainThread senden
Um noch einmal auf das Über- und Freigeben von Instanzen durch, von, über in Verbindung von Thread und Mainthread zu sprechen zu kommen ...
Was soll denn hier erreicht werden? Nehmen wir einmal das Beispiel mit
Delphi-Quellcode:
.
TLogMessage
Wir haben da also einen Thread, der irgendwas bearbeitet und zwischendurch immer wieder etwas melden soll. Dieser Thread soll aber durch das Senden dieser LogMessage nicht mehr als nötig ausgebremst werden (seine Hauptaufgabe besteht eben nicht im Versenden dieser LogMessage). Ok. Warum versendet dieser Thread diese LogMessage denn dann überhaupt? Geben wir diesem Thread doch einen Kumpel an die Hand, der dieses Versenden für ihn übernimmt. Und dieser Kumpel darf ja auch gerne wiederum ein Thread sein (macht ja nichts).
Delphi-Quellcode:
Dieser MessageService übernimmt die Verwaltung für die Message-Instanz, liefert diese Message per Synchronize an wen auch immer aus. Das hat aber dann mit dem eigentlichen Thread, der da am arbeiten ist schon nichts mehr zu tun.
type
TBaseMessage = class abstract end; TNotifyMessageEvent = reference to procedure( Sender : TObject; AMsg : TBaseMessage ); TMessageService = class( TThread ) private FCS : TCriticalSection; FEvent : TEvent; FMessages : TQueue<TBaseMessage>; <--- da kommen die rein FOnMessage : TNotifyMessageEvent; function GetOnMessage : TNotifyMessageEvent; procedure SetOnMessage( const Value : TNotifyMessageEvent ); procedure DoSendMessage( AMessage : TBaseMessage ); protected procedure Execute; override; procedure TerminatedSet; override; public constructor Create; destructor Destroy; override; procedure AddMessage( AMessage : TBaseMessage ); property OnMessage : TNotifyMessageEvent read GetOnMessage write SetOnMessage; end; constructor TMessageService.Create; begin inherited Create( False ); FCS := TCriticalSection.Create; FEvent := TEvent.Create( nil, False, False, '' ); FMessages := TObjectQueue<TBaseMessage>.Create; end; destructor Destroy; begin inherited; FMessages.Free; FEvent.Free; FCS.Free; end; function TMessageService.GetOnMessage : TNotifyMessageEvent; begin FCS.Enter; try Result := FOnMessage; finally FCS.Leave; end; end; procedure TMessageService.SetOnMessage( const Value : TNotifyMessageEvent ); begin FCS.Enter; try FOnMessage := Value; finally FCS.Leave; end; end; procedure TMessageService.AddMessage( AMessage : TBaseMessage ); begin FCS.Enter; try FMessages.Enqueue( AMessage ); FEvent.SetEvent; // <-- Den MessageService aufwecken finally FCS.Leave; end; end; procedure function TMessageService.GetOnMessage : TNotifyMessageEvent; begin FCS.Enter; try Result := FOnMessage; finally FCS.Leave; end; end; procedure TMessageService.TerminatedSet; begin inherited; FEvent.SetEvent; end; procedure TMessageService.DoSendMessage( AMessage : TBaseMessage ); var LOnMessage : TNotifyMessageEvent; begin LOnMessage := OnMessage; if Assigned( LOnMessage ) then LOnMessage( Self, AMessage ); end; procedure TMessageService.Execute; var LMessage : TBaseMessage; begin inherited; while not Terminated do begin FEvent.WaitFor; // Wir warten, bis sich was tut if not Terminated then begin // Nachricht aus der Queue holen FCS.Enter; try LMessage := FMessages.Extract; // Wenn noch Nachrichten in der Queue sind, dann den Event wieder setzen if FMessages.Count > 0 then FEvent.SetEvent; finally FCS.Leave; end; // Nachricht versenden Synchronize( procedure begin DoSendMessage( LMessage ); end ); // Nachricht freigeben LMessage.Free; end; end; Schon ist der Drops gelutscht und jeder macht nur ein wenig und nicht alles oder kreuz und quer durcheinander gewürfelt. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:01 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