Autor |
Beitrag |
reimo
      
Beiträge: 75
|
Verfasst: Di 02.03.04 09:27
Hi,
also folgendes, ich möchte in einem (oder mehreren) von mir während der Laufzeit erzeugten threads eine Instanz einer abgeleiteten Klasse von TForm erzeugen. Der Thread soll solange bestehen bleiben, bis die Form geschlossen wird, ich hab aber keine Ahnung wie ich so etwas implementieren soll, da die Form im neuen Thread auf unterschiedliche Ereignisse reagieren soll ( IP-Connections, Button Clicks,..).
Wie soll die Thread-Funktion ausschauen, damit es funktioniert?
Hab folgendes versucht (schon mit der Gewissheit, dass es nicht funktionieren wird)
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| function ThreadFkf( x:Pointer ): .... var Form2: TFormThread; begin Form2 := TFormThread.Create(...);
while TRUE do begin end; Form2.Free; end; |
mein zweiter Ansatz war, es über Aplication zu versuchen und es funktioniert, aber ich bin mir nicht sicher, ob es auch korrekt ist:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| function TMainForm.ButtonClick..... begin
Application.Initialize; Application.CreateForm(TFormThread, Form2); Application.Run;
end; |
kann ich einfach so die Instanz Application verwenden, oder muss ich meine eigene Instanz von TApplication erzeugen?
Worauf sollte ich bei so einer Erzeugung eines Threads aufpassen?
Falls die zweite Syntax richtig ist, würde mich aber trotzdem interessieren, auf welche weise ich in meinem eigenen Thread eine Form erzeuge?
danke im voraus
reimo
PS.:
Ich weiss, dass ich in der letzten Zeit ziemlich viele Fragen stelle, da ich aber eh schon auf der Suche nach einem ordentlichen Delphi-Buch bin, wird sich dies hofentlich bald einstellen.
Bin leider gezwungen in Delphi 4 zu programmieren, und da noch ein Buch zu finden ist nicht ganz so einfach
Die Delphi-Tutorials von Luckie kenn ich schon
|
|
KidPaddle
      
Beiträge: 351
WinXP,Linux
D7 Prof, XE7
|
Verfasst: Di 02.03.04 10:18
Hallo,
in der Hilfe zu Threads wird darauf hingewiesen, daß alle Nachrichten nur im Kontext des Hauptthread, z. B. nach einem Synchronize - Aufruf, verarbeitete werden sollten. Ich denke dieser Lösungsansatz von Dir wird daher nicht funktionieren.
Gruß
KidPaddle
|
|
Loryn
      
Beiträge: 34
Win 98, ME
D4 Stand.
|
Verfasst: Di 02.03.04 11:18
Hallo,
wenn es "nur" darum geht, die Lebensdauer des Threads an die Lebensdauer der Form zu knüpfen, könnte man die Form TThreadForm im Haupt-Thread erzeugen und im OnClose-Ereignis der Form den Thread beenden, indem man TThread.Terminate aufruft.
Wäre das eine Lösung?
Gruß Loryn
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Di 02.03.04 11:24
@KidPaddle
auf welchen Ansatz von mir beziehst du dich?
durch die Verwendung von:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| function TMainForm.ButtonClick..... begin
Application.Initialize; Application.CreateForm(TFormThread, Form2); Application.Run;
end; |
funktioniert es ja (es wird ein neuer Thread erzeugt), ich weiss aber nicht ob es zulässig ist, was ich da mache (siehe mein erster Post) ?
Und was den ersten Ansatz angeht, glaube ich nicht, dass es nicht funktioniert, über Application wird ja auch ein SubThread erzeugt, der Nachrichten empfangen kann, ob diese dann vom HauptThread weitergereicht werden, oder er diese direkt empfängt ist mir eigentlich egal, hauptsache ist, dass ich eine Form in einem Subthread hab, und ich würde gerne wissen, wie ich soetwas anstelle!
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Di 02.03.04 11:45
@Loryn
danke für den Vorschlag, die Klasse TThreadForm kenn ich noch garnicht, werd sie mir aber gleich anschauen.
wie sieht es aber aus, wenn es nicht um eine Form geht, sondern um eine andere VCL-Komponente:
zB.: Ich leite eine eigene Klasse von TClientSocket ab, oder Kapsle diese nur
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| IPConnection = Class IP: TClientSocket xy: Integer; .. .. .. end;
function ThreadFunction( p: Pointer ).... var IP: IPConnection; begin IP := IPConnection.Create; IP.IP.Active := TRUE; while ... end; end; |
mfg
reimo
|
|
Loryn
      
Beiträge: 34
Win 98, ME
D4 Stand.
|
Verfasst: Di 02.03.04 12:13
@Reimo
Ich habe mich verschrieben, ich meinte mit TThreadForm deine Angabe TFormThread!
Schau doch mal in der Hilfe das Beispiel zu TThread.Terminated an. Dort ist ein Beispiel für TThread.Execute (mit while-Schleife).
Mir ist nicht ganz klar, welche Aufgabe dein Sub-Thread übernehmen soll und wozu der dringend eine eigene Form braucht? Was meinst du mit ThreadFunction? Soll das TThread.Execute sein?
Gruß Loryn
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Di 02.03.04 13:13
Zitat: | Was meinst du mit ThreadFunction? Soll das TThread.Execute sein? |
Ja, im Falle der Klasse TThread wäre das TThread.Execute, es gibt aber noch andere mögkichkeiten einen Thread zu erzeugen zB.: CreateThread(...). Dieser Funktion übergibst du als Parameter den Pointer auf die Thread-Funktion.
Zum Programm:
Also, der Thread soll ein IP-Interface darstellen, d.h. empfangen/verschicken von Daten. Die empfangenen Daten wollte ich in einem Memo-Feld darstellen.
Die Sache mit der Form ist eigentlich sekundär, es geht mir im allgemeinen um das Epfangen von Nachrichten (OnConnect,...) im Subthread, aber anscheinend ist dies ja nicht so einfach.
Ich werde es, wie schon KidPaddle gepostet hat, mal mit Synchronize versuchen, was mir aber nicht gefällt:
es werden alle Daten, die eigentlich nur der SubThread zum Arbeiten benötigt erst über den Hauptthread geschickt -> unnötig
HauptThread.Socket1OnConnect
--> Synchronize( OnConnect ) // unnötige Unterbrechung Hauptthreads
-->Subthread.OnConnect
begin
Haupthread.Socket.ReadBuffer(...) // Und dann noch die direkten Zugriffe auf die Members des Hauptthreads !
end
Vielleicht hab ich das Synchronize-Prinzip falsch verstanden, und ich erzähle nur Mist, also bitte korrigiert mich, falls dem so ist.
@Hilfe-Beispiel bei TThread
Hier wird leider nur das Versenden über ClientSocket gezeigt, aber leider nicht das Empfangen -> OnRead event
Wie wird es eigentlich über die Klasse Application.Run gemacht? hier scheint ja das ganze zu funktionieren
mfg
reimo
|
|
Loryn
      
Beiträge: 34
Win 98, ME
D4 Stand.
|
Verfasst: Di 02.03.04 16:17
@Applikation.Run:
In deiner Projektdatei (Menü Projekt/Quelltext anzeigen) wird so etwas stehen wie:
Delphi-Quelltext 1: 2: 3:
| Application.Initialize; Application.CreateForm(TTestForm, TestForm); Application.Run; |
Application.Run arbeitet sozusagen in einer Endlosschleife alle Windows-Botschaften ab, die für deine Anwendung bestimmt sind. Als Reaktion dieser Botschaften wird z.B. TMainForm.ButtonClick aufgerufen.
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| function TMainForm.ButtonClick..... begin Application.Initialize; Application.CreateForm(TFormThread, Form2); Application.Run; end; |
Wenn du jetzt in TMainForm.ButtonClick erneut Application.Run aufrufst, wird die Botschaftswarteschlange dort abgewickelt, aber es entsteht kein neuer Thread dadurch. Gewissermassen passiert gar nichts Neues, mit der Ausnahme, dass alle Anweisungen, die nach TMainForm.ButtonClick kommen sollten erst abgearbeitet werden können, wenn Application.Run beendet wird; und das ist bei Anwendungsende.
@Empfang von Nachrichten:
Leider kenne ich TClientSocket nicht. Ich verfüge nur über Delphi4 Standard. Ist es richtig, dass bei Empfang einer Nachricht ein OnRead-Event ausgelöst wird? Dann könnte es folgendermassen gehen:
In der while-Schleife deiner Thread-Funktion (z.B. TThread.Execute) fragst du ab, ob Nachrichten da sind. Wenn nicht legst du dich schlafen (z.B. TThread.Suspend). Im TClientSocket.OnRead-Event weckst du den Thread wieder auf (z.B. TThread.Resume). Du landest wieder in der while-Schleife und kannst die anliegende Nachrichte verarbeiten.
@Synchronize:
Ich habe das so verstanden, dass mit Synchronize der konkurrierende Zugriff mehrerer Threads auf ein Objekt vermieden werden soll. Wenn jetzt aber der TClientSocket nur vom Subthread verwendet wird, wieso sollte man Synchronize verwenden und die ganze Abarbeitung über den Hauptthread schicken?
Vielleicht weiss jemand mehr darüber? (Ich lerne gern etwas dazu!)
Gruß Loryn
|
|
KidPaddle
      
Beiträge: 351
WinXP,Linux
D7 Prof, XE7
|
Verfasst: Di 02.03.04 17:38
Ich habe mir für ein Projekt ein SQLMonitor gebastelt, welcher zentral auf einem Server lief. Die SQL - Anweisungen wurden über UDP von den Clients (immerhing an die 40) an den Server geschickt und in einer Memo - Box anzeigt. Verwendet habe ich dafür die Indy - Komponenten, was aber kein Unterschied machen sollte. Die Indy - Komponenten arbeiten mit Threads.
Die ankommenden Daten wurden in einer TStringliste zwischen gespeichert und nicht sofort angezeigt.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| var DataStringStream : TStringStream; begin fLocked := True; try DataStringStream := TStringStream.Create(''); try DataStringStream.CopyFrom(AData, AData.Size); EnterCriticalSection(fLock); try fReceivedData.Add(FormatDateTime('hh:nn:ss:zzz', Now) + ' - ' + ABinding.PeerIP + ' - ' + DataStringStream.DataString); finally LeaveCriticalSection(fLock); end; finally DataStringStream.Free; end; finally fLocked := False; end; |
Wichtig hierbei sind die EnterCriticalSection und LeaveCriticalSection - Aufrufe, die verhindern, das der Hauptthread auf die Variable fReceivedData zu greift. Damit die Daten auch in der Memobox angezeigt werden, nahm ich einen Timer, der regelmäßig die Daten aus fReceivedData an die vorhanden Memo - Einträge anfügt.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| procedure TMainForm.timerUpdateTimer(Sender: TObject); var TempList : TStringlist; begin if (fReceivedData.Count > 0) then begin TempList := TStringlist.Create; try EnterCriticalSection(fLock); try if fLocked then Exit; TempList.Assign(fReceivedData); fReceivedData.Clear; finally LeaveCriticalSection(fLock); end; memoSQLMonitor.Lines.AddStrings(TempList); finally FreeAndNil(TempList); end; end; end; |
Auch hier wird das umkopieren der Daten mit einer EnterCriticalSection/LeaveCriticalSection geschützt, damit nicht während der Zeit für das kopieren neue Daten über die UDP - Schnittstelle angehängt werden.
Das funktioniert sehr gut bei ca. 40 Clients parallel und mit ca. 5-7 neue SQL - Anweisungen pro Sekunde. Da ich nur Objekte verwendet habe, die in die Nachrichtenverarbeitung eingreift, braucht ich mir keine Gedanken über Synchronize machen.
Gruß
KidPaddle
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Di 02.03.04 17:39
Delphi-Quelltext 1:
| In der while-Schleife deiner Thread-Funktion (z.B. TThread.Execute) fragst du ab, ob Nachrichten da sind. |
an so eine Lösung hab ich auch schon gedacht, leider weiss ich nicht wie das Abfragen/verschicken von Nachrichten an/von threads funktioniert. Wäre für jeden Tip dankbar ( schaue mir gerade TEvent an )
Leider find ich diese Lösung auch nicht wirklich sauber, da TClientSocket immer noch ein Member der Hauptform ist, obwohl diese nichts damit anzufangen weiss. Es werden alle Messages/Events über die Mainform an den SubThread geliefert, was ich vermeiden wollte.
TMainForm.ClientSocket.OnRead
begin
// Dazugehörigen Thread aufwecken
// TEvent -> Nachricht schicken, dass Daten zu empfangen sind
end;
Da dieser Thread ein selbständiger Agent sein soll, und je nach Bedarf beliebig viele solcher Agents erzeugt werden können ist der Ansatz der Socketkomponente in der Mainform nicht wirklich das richtige.
Es sollte einfach das Erzeugen einer Instanz reichen und man bräuchte keinen zusätzlichen Code in der Mainform.
zB.:
// Mainform
....
Agent[..] := TAgent.Create( Socket, IP );
Agen.Run;
Um den Rest braucht sich die Mainform nicht kümmern ( im Bezug auf die Schnittstellenanbindung )
@Aplication.Run
Hast recht, jetzt hab ichs erneut ausprobiert, und es funktioniert nicht mehr, keine Ahnung, was ich zuvor für'n Mist baut hab.
Wieso wird aber beim erzeugen einer Form aus der Hauptform ein neuer Thread erzeugt.
SubForm := TSubForm.Create; // neuer Thread laut taskmanager
SubForm2 := TSubForm.Create; // kein neuer Thread mehr ???
wird aber noch eine Form aus der Huptform erzeugt, so ändert sich an der Threadzahl nichts mehr. ???
mfg
reimo
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Di 02.03.04 18:08
@KidPaddle
Ich scheitere aber bereits an der Eventbehandlung(OnRead,..) von einem SubThread aus unter der Verwendung der VCL-Komponenten.
Das heisst, nicht die MainForm soll auf die IP-Events reagieren, sondern der SubThread und die Mainform soll dabei für Usereingaben nicht blockiert werden.
|
|
KidPaddle
      
Beiträge: 351
WinXP,Linux
D7 Prof, XE7
|
Verfasst: Di 02.03.04 18:46
Kannst Du mir das mal zusenden wie Du es umgesetzt hast? Ich schaue es mir gerne an.
Gruß
KidPaddle
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Di 02.03.04 19:27
würd ich gerne machen, hab aber leider nichts richtiges, nach jedem gescheiterten Versuch wird ein neues Projekt angelegt, und auf ein neues. Die Versuche kannst aber haben (erst morgen, muss jetzt leider weg)
Kurz zur erklärung (hab ich aber glub ich schon gepostet)
erzeuge Form1 -> mainform
Form1.Buttonclick -> erzeugt einen Thread
-> in diesem Thread soll eine Klasse erzeugt werden, die TClientSocket beinhaltet (zB Form2*)
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| function threadfunction begin Form2 := TForm2.create; Form2.Socket.Active := TRUE; while XYZ do begin if Socket.SocketReceiveLen > 0 then begin Socket.SocketRecieveBuffer(Buffer....); Memo1.Lines.Add( Buffer ); end else Sleep(100) end; end; |
-> und jetzt baust eine Verbindung zu einem Server auf (von der Klasse, die im SubThread erzeugt wurde), dieser sendet zum Beispiel Zustandsdaten
-> gibst die Daten in Form2 aus
-> der Client läuft, bis die Application beendet wird.
*)
Delphi-Quelltext 1: 2: 3:
| Form2 = class(TForm) .. Socket: TClientSocket |
sers,
reimo
|
|
Motzi
      
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: Di 02.03.04 20:04
Die VCL ist nicht threadsafe und es funktioniert auch nicht eine Form komplett in einem anderen Thread laufen zu lassen..! Ich hab mal vor längerer Zeit eine ähnliche Frage im Entwickler-Forum gestellt... hier ist der Link
_________________ gringo pussy cats - eef i see you i will pull your tail out by eets roots!
|
|
Loryn
      
Beiträge: 34
Win 98, ME
D4 Stand.
|
Verfasst: Di 02.03.04 23:20
@Motzi
Ich habe in der Delphi-Hilfe über Threads nachgelesen und bin auch deinem Link nachgegangen. Soweit habe ich alles verstanden.
Der Vollständigkeit halber eine abschliessende Frage: Welche Delphi-Klassen sind threadsicher? Alle Klassen, die nicht zur VCL gehören? Woran ist das zu erkennen? Z.B. alles was von TControl abstammt ist nicht threadsicher, der Rest aber doch?
Gruß Loryn
|
|
reimo 
      
Beiträge: 75
|
Verfasst: Mi 03.03.04 10:43
Hi,
Danke für die Antworten, werde wohl die IP-Connection selber schreiben müssen und auf die Form/VCL verzichten.
mfg
reimo
|
|
Motzi
      
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: Mi 03.03.04 14:49
Loryn hat folgendes geschrieben: | Der Vollständigkeit halber eine abschliessende Frage: Welche Delphi-Klassen sind threadsicher? Alle Klassen, die nicht zur VCL gehören? Woran ist das zu erkennen? Z.B. alles was von TControl abstammt ist nicht threadsicher, der Rest aber doch? |
Das kann man so nicht beantworten... ich weiß jetzt nicht so ganz genau welche VCL-Klassen jetzt intern wie funktionieren.. auf auf jeden Fall kann man mal sagen, dass alle GUI-Controls mit denen man sich im Form-Designer so schön herumspielen kann nicht threadsafe sind und nachdem der "kleinste gemeinsame Nenner" TComponent ist würd ich mal sagen bei allen Nachfahren TComponent muss man aufpassen... will das aber hier nicht zu verallgemeinern und es kommt teilweise auch sicher darauf an wie man das ganze einsetzt...
_________________ gringo pussy cats - eef i see you i will pull your tail out by eets roots!
|
|
|