![]() |
String von Thread an Programm senden - Stilfrage!
Hallo,
ich habe einen Thread, der einen String an die Hauptform senden soll. Da es sich nur um einen String handelt, möchte ich kein WM_COPYDATA verwenden. Nun habe ich eine Lösung gefunden, frage mich aber, ob diese "sauber" genug ist. Gerne nehme ich Verbesserungsvorschläge an! Das Prinzip ist folgendes: Ich habe in der Hauptanwendung eine globale Stringvariable. Den Pointer übergebe ich an den Thread. Dieser erstellt einen String und greift direkt auf den Pointer zu und verändert die Variable. (Es ist sichergestellt, dass sich an dem Pointer und an der Inhalt der Variable in dieser Zeit nichts ändert). Danach kann ich normal auf diese Variable zugreifen. Ist das so in Ordnung?
Delphi-Quellcode:
var
s_thread: String; .. .. procedure TForm1.Button1Click(Sender: TObject); var Th: TMyThread; begin Th: TMyThread.Create(True); Th.s:=@s_thread; Th.resume; end;
Delphi-Quellcode:
Danke im Voraus
procedure TMyThread.Execute;
var str: String; begin str:='Hallo, ich bin ein Test'; s^:=str; end; |
Re: String von Thread an Programm senden - Stilfrage!
Nö, das geht so nicht.
Du musst schon das schreiben Synchronisieren und nicht nur den Zeiger auf den String kopieren. Du kannst: 1. Anstatt WM_CopyData auch jede andere Message nehmen und synchronisiert (sendmessage) nen pointer auf die Adresse senden. Wobei das mit dem pointer nicht notwendig ist (Message alleine reicht), da in dem Moment auch der MainThread auf alle public Felder und Properties des TThreads zugreifen kann. Dein Thread wird ja aufgrund des sendmessage angehalten. 2. Du rufst mittels synchronize eine Methode des MainThreads auf und kannst von da auf die gesamte Klasse TThread zugreifen. 3. Eine Schöne Idee ist auch für solche "globalen" AustauschVariablen eine Klasse zu schaffen:
Delphi-Quellcode:
Das Konzept habe ich selber noch nie angewandt, aber es wäre auch eine Möglichkeit. Mit Messages muss man ja auch mächtig aufpassen, dass man keine Threads gegenseitig locked. Das Problem hast du damit nicht. Intern wird hier mit Events gearbeitet.
uses sysutils, classes;
type TContainer=class(TMultiReadExclusiveWriteSynchronizer) //oder =class(TSimpleRWSync) private FmyVariable:string; FOnChange:TNotifyEvent; //Vielleicht hier auch eine Liste function getmyVariable:string; procedure setmyVariable(value:string); function getOnChange:TNotifyEvent; procedure setOnChange(value:TNotifyEvent); procedure DoOnChange; public property myVariable:string read getmyVariable write setmyVariable; property OnChange:TNotifyEvent read getOnChange write setonchange; end; implementation function Tcontainer.getmyVariable:string; begin beginread; result:=FmyVariable; endread; end; procedure TContainer.setmyVariable(value:string); begin beginwrite; FmyVariable:=value; endwrite; DoOnChange; end; function Tcontainer.getOnChange:TNotifyEvent; begin beginread; result:=FOnChange; endread; end; procedure Tcontainer.setOnChange(Value:TNotifyEvent); begin beginwrite; FOnChange:=Value; endwrite; end; procedure Tcontainer.DoOnChange; var tempOnChange:TNotifyEvent; begin tempOnChange:=OnChange; if assigned(tempOnChange) then TempOnChange(self); end; Alternativ kannst du dieselbe Klasse auch von "TSimpleRWSync" ableiten. SimpleRWSync ist, wenn du paralleles Lesen von Threads eher selten hast. Dadurch wird die ganze Klasse etwas schneller. SimpleRWSync kapselt CriticalSections. |
Re: String von Thread an Programm senden - Stilfrage!
Ok... meine Lösung hatte zwar funktioniert, aber wenn du davon abrätst ist das ok.
Ich habe jetzt auch gesehen, dass ich direkt von der Anwendung einen String an den Thread schicken kann. Ich dachte das würde auch zu Fehlern wie bei der Stringübergabe zwischen DLL und EXE kommen. Doch das ist nicht so. Also sende ich einen String vom MainThread an den eigentlichen Thread via globaler Variable und durch sendmessage wieder zurück. Das dürfte das ok sein, oder? Da es sich nur um eine einzige Variable handelt, bin ich mit der jetzigen Lösung zufrieden. So können doch keine Fehler auftreten, oder? |
Re: String von Thread an Programm senden - Stilfrage!
Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
procedure TThread.execute;
begin ... self.s:='Hallo'; sendmessage(mainhandle,WM_threadsomething,0,0); //jetzt beinhaltet self.s einen neuen Wert ... end; procedure TForm1.getThreadMessage(var msg:TMessage); //message WM_threadsomething begin //Da der Thread jetzt schläft, kannst du ohne Risiko auf alle Variablen des threads zugreifen showmessage(Thread.s); Thread.s:='gelesen'; end; Zitat:
|
Re: String von Thread an Programm senden - Stilfrage!
Eins der immer noch gültigen Grundprinzipien modularer Programmierung sind die Datenmodule. Hinter diesem Konzept verbergen sich Container, die Daten enthalten, die von den Modulen gemeinsam benutzt und verändert werden. Daher hat Delphi auch das TDatamodule-Konzept, obwohl das hier nur konzeptionell vergleichbar ist: Also jetzt nicht an TDatamodule denken.
Anders ausgedrückt sollt Du dich schnellstmöglich von der Methapher 'Senden von Daten' verabschieden. Die Daten sind irgendwo in einem Datenmodul und da sind sie auch gut aufgehoben. Die der Vorschlag von Sirius geht ja genau in diese Richtung. Du hast also 'Container' (Datenmodule), die die Daten speichern und über geeignete Methoden sicherst Du konfliktfreien Zugang ztu den Daten. Deine Threads verändern also asynchron diese Daten und teilen dann anderen Threads mit, das die Arbeit beendet ist, und die Weiterverarbeitung starten kann. Ob man das mit Messages oder Events löst, bleibt Dir überlassen. Du könntest Dir auch vorstellen, das ein Thread immer einen Teil abarbeitet, dann einem 2.Thread mitteilt, das es etwas zu tun gibt. Der macht dann weiter, während sich Thread#1 wieder um den nächsten Happen kümmert usw. Dabei werden keine Daten verschickt, sondern nur Nachrichten. Wenn Du an Anwendungsentwicklung im Team denkst, wird Code ja auch gemeinsam über ein CVS verwaltet und nicht immer komplett versendet... |
Re: String von Thread an Programm senden - Stilfrage!
Vielen Dank für eure Mühe, doch der letzte Beitrag hat mich nun wieder verunsichert? Ist die aktuelle Methode, dich ich oben beschrieben habe und auch nutze, jetzt verwendbar oder nicht. Das konnte ich jetzt nicht rauslesen. :oops:
Zitat:
|
Re: String von Thread an Programm senden - Stilfrage!
Verwendbar (mit genannten Einschränkungen) ist sie. aber du kannst eben auch OOP-konformer arbeiten. Dadurch gewöhnst du dir gleich die richtige Arbeitsweise an.
Du kannst zum Beispiel die Klasse TCotainer im Mainthread instanzieren und dann die Instanz an den Thread übergeben. Und hier kannst du problemlos auf die Propertys zugreifen und ich habe auch ein OnChange Ereignis eingebaut. diese beginwrite und endwrite (sowie *read) Abschnitte verhindern eben das gleichzeitige Schreiben mehrere Threads. |
Re: String von Thread an Programm senden - Stilfrage!
Hm...ich weis nicht ob ich das alles gerade richtig verstanden habe... Was würde bei meiner Lösung passieren, wenn ich mehrere Threads gleichzeit hätte (warum auch immer)?
|
Re: String von Thread an Programm senden - Stilfrage!
Na durch die Kapselung der Zugrissmethoden machst Du deinen Container (altdeutsch 'Datenmodul') ja ggf. threadsicher. Das ist doch gerade das Gute an so einem Container bzw. der ganzen OOP-Chose. Wie Du genau den Zugriff auf die Daten implementierst (Datenbank, synchronisiert oder ob Du sie immer life vom Mond holst) ist gekapselt. Da man gar nicht mehr direkt an die Rohdaten herankommt, musst Du dir nie wieder Gedanken über Threadsicherheit machen (sofern Du den Zugriff eben synchronisierst).
Befolge doch einfach sirius' Rat, instantiiere den Container und übergebe dem Thread diese Instanz. Der Zugriff auf die konkreten Daten ist doch threadsicher. |
Re: String von Thread an Programm senden - Stilfrage!
Ok, jetzt habe ich glaub ich verstanden. Ich werde die Lösung einbauen. Komme aber erst wieder am Sonntag dazu.
Danke für eure Hilfe / Geduld! |
Re: String von Thread an Programm senden - Stilfrage!
Hallo,
hab jetzt doch noch eine Frage. Und zwar verstehe ich nicht, wie der Thread oder das Mainform mit der Klasse arbeitet. Ich habe die Klasse da stehen, aber wie verändere ich jetzt via Thread oder MainForm variablen? Und wo liegen diese Variablen? Ist es so, dass diese Variablen IN der Klasse liegen und auch DORT verändert werden? Der Thread greift dann auf die Klasse zu und nimmt sich die Variablen? Wo wird die Klasse erstellt und instantiiert? Hab das jetzt alles gesehen und im Kopf verarbeitet, aber die Klasse steht da immernoch (in mir drin) zusammenhanglos rum. Kann auch daran liegen, dass es schon ziemlich spät ist, aber ich versteh's nicht so ganz Danke |
Re: String von Thread an Programm senden - Stilfrage!
Morgen,
ungefähr so müsste es sein:
Delphi-Quellcode:
type
TForm1=class(TForm) private DataContainer: TContainer; DataContainerChange(Sender: TObject); end; procedure TForm1.Create(Sender: TObject); begin DataContainer := TContainer.Create; DataContainer.OnChange := DataContainerChange; end; procedure TForm1.DataContainerChange(Sender: TObject); begin ShowMessage('Thread hat Daten verändert :-)'); end; procedure TForm1.Button1Click(Sender: TObject); var Th: TMyThread; begin Th: TMyThread.Create(True); // Th.s:=@s_thread; Th.DataContainer := DataContainer; Th.resume; end; Und im TThread.Execute kannst du dann eben mit
Delphi-Quellcode:
Die Daten verändern, und der Hauptthread wird sogar über die Änderung informiert.
DataContainer.myVariable := 'threaddaten!';
So habe ich es jedenfalls verstanden. Und der Code hier ist nur so geschrieben, bestimmt ein Fehler drin :mrgreen: |
Re: String von Thread an Programm senden - Stilfrage!
Zitat:
@DJ-SPM Letztenendes ist dass nur in String in eine Klasse gakpaselt. Und die übergibst dann nur die Klasse und kannst den String von außen Threadsicher ändern. |
Re: String von Thread an Programm senden - Stilfrage!
Das Beispiel ist klasse! Ich habe es jetzt glaub ich verstanden.
Zu dumm, dass ich es morgen testen kann, da ich jetzt auf eine Hochzeit im anderen Ende Deutschlands fahre :twisted: Naja, wird bestimmt auch lustig. Aber solche Prolematiken beschäftigen mich immer so lange, bis "ich" sie gelöst habe! Danke jedenfalls! |
Re: String von Thread an Programm senden - Stilfrage!
ich würde keine globale Variable nehmen sondern eine membervariable. Aber davon unabhängig reicht es wenn du den Zugriff auf den String mit einer CriticalSection absicherst.
|
Re: String von Thread an Programm senden - Stilfrage!
Zitat:
Zitat:
|
Re: String von Thread an Programm senden - Stilfrage!
Bin wieder da!!!
Nochmal eine Frage zur "Container-Lösung"... Habe sie jetzt implementiert und die Container-Klasse als Extra-Unit mit in den Thread und in die Applikation eingebunden. Du hast aber die Methode "setMyVariable" als private deklariert. So kann ich ja nicht vom Thread oder von UnitX auf diese Methode zugreifen - oder mache ich was falsch? Vielen Dank nochmals für den Code! |
Re: String von Thread an Programm senden - Stilfrage!
Dafür gibts ja das Property "myVariable". Dieses ruft im schreibenden Fall setmyVariable und im lesenden Fall getmyVariable auf.
Du solltest dir mal propertys anschauen, und wie die funktionieren :zwinker: Du brauchst also nur myVariable zu nutzen. Schau dir mal an, was "wicht" geschrieben hat! (Da ist nur ein winziger Fehler drin) |
Re: String von Thread an Programm senden - Stilfrage!
Ja, ich habe es genauso gemacht, aber auf diese Art und weise "hängt" sich der Thread auf, wenn ich die Variable ändere.
Thread:
Delphi-Quellcode:
Hauptprogramm
public
DataContainer: TContainer; end; procedure Execute(...) begin DataContainer.myVariable:='Hallo'; end;
Delphi-Quellcode:
Doch der Thread wird nicht bis zum Ende ausgeführt und die DataContainerChange wird auch nicht aufgerufen. Liegt das daran, dass die Anwendung, die den Thread aufruft, eine DLL mit DLL-Form ist und per Windows.SetParent in das Hauptprogramm eingebunden wird?
Th := TMyThread.create(true);
Th.DataContainer:=dataContainer; Th.Resume; |
Re: String von Thread an Programm senden - Stilfrage!
Hmm... Vielleicht liegts daran:
Im Thread=>SetMyVariable=>OnDataChange-Ereignis wird ausgelöst=>Im OnDataChange-Event wird die VCL benutzt => *aufhäng* |
Re: String von Thread an Programm senden - Stilfrage!
Kann ich das irgendwie synchronisieren?
|
Re: String von Thread an Programm senden - Stilfrage!
Warum willst du es so umständlich machen? Mit Synchronize geht es doch ganz einfach. Also genau so, wie du es zu Anfang schon geschrieben hast, nur dass du statt
Delphi-Quellcode:
einfach
procedure TMyThread.Execute;
var str: String; begin str:='Hallo, ich bin ein Test'; s^:=str; end;
Delphi-Quellcode:
schreibst.
procedure TMyThread.foo;
var str: String; begin str:='Hallo, ich bin ein Test'; s^:=str; end; procedure TMyThread.Execute; begin Synchronize(foo); end; |
Re: String von Thread an Programm senden - Stilfrage!
Ok, das wäre auch eine Idee, aber in der anderen Lösung wird die Variable lesend oder/und schreibend geschützt, sodass nahezu alle Komplikationen ausgeschlossen sind.
Angenommen der User klickt schnell im Formular rum und löst 2 Threads aus, die Daten empfangen und schreiben beide auf die gleiche Variable, so wird mindestens eine Prozedur nicht richtig ausgeführt, da die Informationen schon durch den anderen Thread überschrieben worden sind. So müsste das eigentlich sein, wenn ich das jetzt alles richtig verstanden habe. Und da dieses Szenario durchaus leicht vorkommen kann, gehe ich lieber auf "Nummer sicher". |
Re: String von Thread an Programm senden - Stilfrage!
Synchronisiere erstmal den Zugriff auf die Container-Eigenschaft 'MyVariable'.
@Macci: Wenn Du weiterhin Anwendungen entwickeln willst, die sporadisch abk***en, dann nur weiter so. Wenn nicht, dann verwende solche Kontainerklassen. :zwinker: |
Re: String von Thread an Programm senden - Stilfrage!
Habe den Zugriff mittels weiterer Prozedur "ChangeVar" und dem Aufruf "Synchronzie(ChangeVar)" aus Execute synchronisiert - allerdings ohne Erfolg!
|
Re: String von Thread an Programm senden - Stilfrage!
Zitat:
An alzaimar: Falsch, wenn man vermeiden will, dass Programme "sporadisch abkacken" (=nichtdeterministisches Verhalten wegen der Nebenläufigkeit von Threads entsteht) sollte man dafür Semaphore oder Mutex (=binäre Semapohore) verwenden. Genau das passiert im Grunde bei einer synchronisierten Methode. Deshalb verstehe ich nicht, wo du da ein Problem sieht. |
Re: String von Thread an Programm senden - Stilfrage!
Also, die Ursache des Problems - unabhängig von eurem "Prinzipstreit" (nicht böse gemeint :wink: ) - bei der vorgeschlagenen ContainerLösung scheint wirklich der Aufruf der DLL mit Windows.SetParent zu sein.
Ich habe ein neues Beispielprojekt erstellt, die Variablenänderung nochmals Synchronisiert und es funktioniert. Aber was mach ich nun? Würde gerne die Einbindung des Moduls in den festen Bereich der Hauptanwendung behalten. |
Re: String von Thread an Programm senden - Stilfrage!
Hi DJ-SPM,
du kannst ja mal folgendes ausprobieren, probeweise:
Delphi-Quellcode:
Sobald du jetzt den Programmablauf des Threads TTest anstößt (z.B. mit TTest.Create(False); ) wird das ganze Programme festhängen - der Nutzer kann in dieser Zeit nirgendswo hinklicken. Brauchst dir also keine Sorgen machen, dass gleichzeitig mehrmals auf den String schreibend zugegriffen wird.
procedure TTest.Execute;
begin { Thread-Code hier plazieren } Synchronize(foo); end; procedure TTest.Foo; begin while true do; end; |
Re: String von Thread an Programm senden - Stilfrage!
Liste der Anhänge anzeigen (Anzahl: 1)
Ja, du musst natürlich in dem NotifyEvent aufpassen, dass du da immer noch im Thread bist.
Wenn du das Event, dass sich eine Variable geändert hat, lieber im MainThread ausführen willst, müsste man wahrscheinlich mit eine Message arbeiten. Oder auch einen Thread erstellen, der alle Threads verwaltet. Ich habe mal im Anhang die mit einer asynchronen Message (asynchrone Messages sind risikofrei) veränderten TContainer-Klasse in einem Test-Programm. Edit: Synchronize in einer DLL funktioniert nie. Edit2: Und ich habe die Ableitung mal von TMultiReadWrite.... auf TSimpleRWSync geändert. |
Re: String von Thread an Programm senden - Stilfrage!
Zitat:
Mit Messages kann ich soweit umgehen (denke ich). Ich verwende sie zum Beispiel um die DLL wieder ordentlich zu entladen, wenn der Benutzer das Modul schließt. Ich schaue mir das Beispiel gerade mal an. Ich danke euch nochmal für die Mühe, die ihr euch macht! |
Re: String von Thread an Programm senden - Stilfrage!
Zitat:
Ob mein Konzept in einer DLL funktioniert kann ich jetzt auf Anhieb nicht sagen. Wenn du von SimpleRWSync ableitest (also einfach nur Critcal Sections verwendest), dann dürfte es kein Problem sein. Eher noch mit der zuerst geposteten Variante ohne Messages. |
Re: String von Thread an Programm senden - Stilfrage!
Wenn du mit einer DDL arbeitest, bastle dir doch einfach selbst ein Semaphor. Das geht recht einfach. Hab sowas in Delphi zwar noch nie gebraucht, aber letztens mal in Java, aber das Prinzip ist natürlich das gleiche.
|
Re: String von Thread an Programm senden - Stilfrage!
Ähm.. was ist das? Die Lösung mit der Message gefällt mir. Aber erkläre mir das doch bitte mal!
|
Re: String von Thread an Programm senden - Stilfrage!
Also ein Semaphore funktioniert vom Prinzip her wie ein Schüsselchen mit Murmeln drin ;-) Immer wenn ein Programmteil einen kritischen Abschnitt, also einen Abschnitt der nie beliebig oft gleichzeitg/nebenläufig ausgeführt werden darf, betreten will geschieht folgendes:
Der laufende Thread schaut nach, ob noch eine Murmel in der Schüssel drin ist, wenn ja, nimmt er sie raus und betritt den kritischen Bereich. Wenn nein, wartet er (entweder aktiv oder besser: passiv), bis wenigstens eine Murmel drin ist. Sobald er den kritischen Abschnitt verlassen hat, legt er diese Murmel zurück. Hast du jetzt ein Schüsselchen mit genau einer Murmel drin, spricht von man von einem binären Semaphor oder auch Mutex. Wenn du willst, kann ich dir mal zeigen, wie man sowas in Java machen würde. In Delphi übersetzen tu ich es aber nicht, geht aber einfach ;-) |
Re: String von Thread an Programm senden - Stilfrage!
Java bringt mir eigentlich jetzt nix. Aber eine Frage. Mache ich das nicht mit der Container-Unit und den BeginRead / BeginWrite bzw. End*? Ich sperre den Zugriff auf die Murmeln so, dass nur einer mit den Murmeln spielen darf. Hat er keinen Bock mehr zu spielen, geb ich den Bereich wieder frei - oder verstehe ich das jetzt falsch?
|
Re: String von Thread an Programm senden - Stilfrage!
Ja, ist schon richtig so. Aber du musst halt aufpassen, dass der Vorgang des Murmel-Rausnehmens und Zurücklegens auf keinen Fall unterbrochen wird, denn dieser Vorgang selbst ist ja keine atomare Aktion und der Scheduler könnte dem Thread genau in dem Moment den Prozessor entziehen, wenn er gerade dabei war, die Murmel zurückzulegen, und dann käme es zu einem Deadlock.
|
Re: String von Thread an Programm senden - Stilfrage!
Halte ich aber für eher unwahrscheinlich. Ist auch keine große Sache die im Thread passiert.
Nur eine Frage zum Beispiel aus dem angehängten Quellcode. Da wurde als Message eine solche verwendet:
Delphi-Quellcode:
Hat das einen speziellen Grund, oder könnte ich auch
const CM_OnChange=WM_User;
Delphi-Quellcode:
benutzen?
const
WM_ThreadChange = WM_USER + 71 Diese Variante mit der Message funktioniert auf jeden Fall! Hab mir grad die Delphi-Hilfe zu Rate gezogen, um die Methoden nachvollziehen zu können |
Re: String von Thread an Programm senden - Stilfrage!
Also so ganz verstehe ich nicht. Warum willst du lieber die 2. Variante benutzen? Und warum sollte es nicht funktionieren? Probier es doch mal aus ;-)
|
Re: String von Thread an Programm senden - Stilfrage!
Das war jetzt wieder so eine Stilfrage. Ich definiere eigene Messages immer so, wie ich es in der 2. Variante geschrieben habe. Hab's halt so gelernt. Deshalb hatte mich die 1.Variante auch etwas verwundert und ich dachte, dass das einen speziellen Grund hat.
|
Re: String von Thread an Programm senden - Stilfrage!
Müsste genauso gehen, wenn du deine 2. Variante benutzt :-) Der Semaphore ist hier übrigens im "beginwrite" (murmel herausnehmen) und "endwrite" (murmel zurücklegen) versteckt :-) Würdest du keine DLL verwenden, ginge es ganz einfach mit Synchronize (funktioniert auch wie ein Semaphor).
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:28 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