| Autor |
Beitrag |
Kay E.
      
Beiträge: 118
|
Verfasst: Do 20.01.11 19:38
Hallo!
Ich schreibe an einem Programm, bei dem der User in einer Form Einstellungen in einer Form machen kann. Auf dieser Form (ab jetzt: Form1) befindet sich ein (Start-)Button, der die nächste Form (aka. Form 2) showed und die aktuelle hided (alle notwendigen Formulare sind zu dem Zeitpunkt schon erzeugt, aber gehided). Sobald das Form2 angezeigt wird, soll mit dem Prozess angefangen werden, der gerne mal ein paar Stunden gehen kann.
Naheliegenderweise hab ich die Prozedur in Form2.Show gepackt. Das Problem dabei ist, dass das Formular ja erst dann gezeichnet wird, wenn Form2.Show abgehandelt ist - also NACH dem ganzen Prozess. Da aber grafische Rückmeldung für den user auf dem Fom2 abläuft, muss es zwingend angezeigt werden.
Ich hab das problem zeitweise umgangen, indem ich einen Timer auf auf 500 ms stell und ihn einmal triggern lass, sobald Form2.Show eintritt. In Timer.Timer wird dann die lange Prozedur aufgerufen. Das kann ja aber nicht der Weisheit letzter Schluss sein.
Wie kann ich das Problem umgehen? Oder ist das Designtechnisch alles Mist? Wenn ja, wie gestalte ich sowas am sinnvollsten?
Danke schonmal!
Grüße Kay
|
|
Bergmann89
      
Beiträge: 1742
Erhaltene Danke: 72
Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
|
Verfasst: Do 20.01.11 19:42
Hey,
dafür gibts Threads. Einfach mal googlen, dazu findet sich haufen Zeug. Als kurze erklärung: du leitest eine Klasse von TThread ab und überschreibst die Execute-Methode. Da packst du dann dein Code rein, der ausgeführt werden soll. Sobald diese Methode dann zuende gerechnet hat wird der Thread terminiert und meldet das, sodass du dementsprechend darauf reagieren kannst.
MfG Bergmann
_________________ Ich weiß nicht viel, lern aber dafür umso schneller^^
|
|
Kay E. 
      
Beiträge: 118
|
Verfasst: Do 20.01.11 19:53
*määäh* Threads wollte ich eigentlich vermeiden :\ Die produzieren so gern hässliche Fehlermeldungen  Und da das eine Apparatur ist, die zwingend mehrere Stunden ohne Fehler durchlaufen muss, wollt ich die wenn möglich umgehen.
Sonst gibt's keine schöne Möglichkeit?
Grüße Kay
|
|
Bergmann89
      
Beiträge: 1742
Erhaltene Danke: 72
Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
|
Verfasst: Do 20.01.11 19:56
Hey,
wenn man die richtig verwendet, dann machen die keine hässlichen Fehlermeldungen
Ne andere Lösung fällt mir dazu jetzt nicht ein...
€: höchstens ne 2. Anwendung, die dann mit der 1. die Daten aushtauscht die gebraucht werden. Aber da kannst du auch Threads nehmen, das kommt aus selbe raus und ist auf alle Fälle sinnvoller als 2 Anwendungen.
MfG Bergmann
_________________ Ich weiß nicht viel, lern aber dafür umso schneller^^
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: Do 20.01.11 20:48
Ich weiss nicht, welche Version du verwendest, aber was du vielleicht suchst ist OnActivate.
Aber: Als User möchte ich nicht, dass die UI blockiert. Eine Aktion darf die UI meiner Meinung nach höchstens eine halbe Sekunde blockieren, ausnahmsweise vielleicht mal eine Sekunde. Nach 5 Sekunden wird dein Programm als "not responding" markiert.
An Threads ist nichts falsch und es kommen auch garantiert keine komischen Fehlermeldungen, ausser du machst Sachen, die du nicht tun solltest. Wenn man lokal abgeschlossen was rechnet und dort nicht irgendwie auf die UI zugreift, muss man auch nichts Spezielles berücksichtigen.
|
|
jaenicke
      
Beiträge: 19346
Erhaltene Danke: 1754
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 20.01.11 21:01
Eine Alternative um den speziellen Fall zu behandeln:
Wenn du nach dem Show im Code weitermachst und auch Rechenzeit mit Application.ProcessMessages freigibst, wird das Formular schon angezeigt. Du darfst eben nur den Code nicht in dein zweites Formular packen. Da hat er aber ohnehin nichts zu suchen, da das ja nur zur Fortschrittsanzeige dient.
Also vom Prinzip her: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| ProgressForm := TProgressForm.Create(Self); try ProgressForm.Show; ProgressForm.Progress := 1; CalcSomething; ProgressForm.Progress := 2; ... ProgressForm.Close; finally ProgressForm.Free; end; |
Trotzdem sind Threads die weitaus bessere Variante...
|
|
platzwart
      
Beiträge: 1054
Erhaltene Danke: 78
Win 7, Ubuntu 9.10
Delphi 2007 Pro, C++, Qt
|
Verfasst: Fr 21.01.11 01:07
Und wenn du die Aufgabe aufsplitten kannst, dann mach doch mehrere Threads. Dann kann das ganze auf mehreren CPU-Kernen parallel abgearbeitet werden und somit ist das ganze viel schneller.
_________________ Wissenschaft schafft Wissenschaft, denn Wissenschaft ist Wissenschaft, die mit Wissen und Schaffen Wissen schafft. (myself)
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Fr 21.01.11 04:32
Abhängig von der Aufgabe wäre es ggf. sogar möglich Fibers zu verwenden, bzw. Thread-Pools. Aber dazu fehlen hier grad noch ein paar Angaben. Hast Du mal etwas (ggf. vereinfachten) Source da? Daraus sollte ggf. erkennbar sein, wie das Updaten funktioniert, bzw. wie einzelne Rechenschritte damit zusammenhängen. Generell gilt aber: Aufwendige Tasks dieser Länge gehören in Threads. Punkt.
Und wenn Du hässliche Fehlermeldungen bekommst, machst Du was falsch. 
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Kay E. 
      
Beiträge: 118
|
Verfasst: Fr 21.01.11 14:20
Hey, vielen Dank für die vielen Antworten.
Dann will ich mal weiter ausholen, um den Zusammenhang klarzumachen. Den Code (auch in vereinfachter Form) wird ziemlich unmöglich sein, hier rein zu stellen. Sind dann doch schon einige Tausend Zeilen Code.
Ich bleib bei der Nomenklatur vom ersten Post, also Form1 und Form2.
Das ganze Programm ist eine Ansteuerung für ein von mir in meiner Diplomarbeit gebautes Gerät. Das Prinzip dieses geräts ist es, verschiedene pneumatische Ventile sequenziell für vom User spzifizierte Zeiten zu öffnen und das wiederholt sich x-mal. Wobei x hier in der Größenordnung von 200-500 ist, so dass das ganze mal ne Nacht lang durchläuft. Die Ansteuerung der Ventile läuft über ne AD/DA-Wandler-Karte.
So... Wenn das Programm läuft, kann der User seine Einstellungen auf Form1 machen. Betätigt er den "Start Process"-Button, kommen ein paar Sicherheitsabfragen und danach soll Form1 gehided und Form2 geshowed werden. Zur Zeit ist in der Form2.Show der Aufruf zum eigentlichen Prozess untergebracht. Der unterteilt sich in mehrere kleine Schritte, wobei das immer drauf raus läuft, dass ein Ventil geöffnet wird, für y Sekunden offen bleibt (was ich mit einem sleep(y) realisiere) und danach ein Wert an die Karte geschrieben wird, dass das Ventil wieder geschlossen wird. Ein Timer kann ich statt des sleep nicht nehmen, da die TimerKomponente für den Versuchsaufbau zu ungenau ist.
Auf Form2 soll der Fortschritt des Prozesses periodisch aktualisiert werden (immer anch gewissen Prozessschritten) und der User soll optische Rückmeldung über den Prozess bekommen (LED an/aus, wenn Ventil offen/zu, etc). Desweiteren soll regelmäßig in einem Logfile der Fortschritt des Prozesses festgehalten werden. jede aufgerufene Prozedur schreibt auch noch in ein Programm-Logfile zum späteren Debuggen an der Maschine.
Auf Form2 gibt es auch ncoh einen Abbruch-Button, der zwar immer ansprechbar sein soll, den Prozess aber nur zu sicheren Zeiten stoppt und das Gerät in einen sicheren StandBy fahren kann.
Das ganze ist garniert mit einer umfangreichen Exception-Behandlung an der ich derzeit noch ein bisschen zu knabbern hab, aber das ist ein anderes Problem
Ich denke, alles in allem werde ich um Threads nicht herum kommen. Eigentlich wollte ich es vermeiden, denn wenn ich mit der DA fertig bin, wird das Programm jemand warten müssen, der von Delphi / programmierung im Allgemeinen keine große Ahnung hat. Da wollte ich das Programm so einfach wie möglich gestalten, sodass es zwar vlt. nicht elegant ist, aber dafür nachvollziehbarer. Aber in dem Fall müssten dann alle Benutzer des Programms drunter leiden. Aber für Vorschläge bin ich gerne offen  Bei Fragen zu der Maschine oder dem Programm natürlich einfach fragen!
Ich hab bisher nur in einer Vorlesung über C mit Threads (eigentlich Forks) gearbeitet. In Delphi hab ich das noch nie gebraucht. Habt ihr mir da irgendwelche Links zu Tutorials die ihr empfehlen könnt?
Das Demo-Programm von Delphi gibt ja jetzt nicht allzu viel her.
|
|
Xion
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Fr 21.01.11 14:25
Die Lösung ist ein einfaches Application.ProcessMessages in der langen procedure, oder irre ich mich?
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
Kay E. 
      
Beiträge: 118
|
Verfasst: Fr 21.01.11 14:34
Davon hab ich haufenweise in der langen Prozedur verteilt (genauer: jedesmal vor und nach dem Öffnen/schließen eines Ventils. Auch ein ProcessMessages in der FormShow vor Aufruf der langen Przedur zeichnet das Form nicht.
Prinzipiell arbeiten die schaltflächen während des Prozesses, aber sie ändern ihren Zustand halt nur, wenn der Prozess gerade nicht in einem sleep hängt. Dann kann das schon sein, dass der benutzer mal ne längere Zeit warten muss, bis die Schaltfläche reagiert. Die Ventile sind in der Größenordnung von Sekunden geöffnet.
Grüße Kay
|
|
jaenicke
      
Beiträge: 19346
Erhaltene Danke: 1754
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Fr 21.01.11 15:05
Kay E. hat folgendes geschrieben : | | wenn der Prozess gerade nicht in einem sleep hängt |
Wozu Sleep?
www.delphipraxis.net/6620-delay.html
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Fr 21.01.11 19:22
Grob hätten wir für einen Thread also:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
| type TVentilThread = class(TThread) private FPosition: Integer; FOpenCloseTimes: array of Integer;
FOnProgress: TVentilProgressEvent;
procedure SyncShowProgress; procedure ThreadReleaseSteam; procedure ThreadWithholdSteam; public procedure Execute; override;
property OnProgress: TVentilProgressEvent read FProgress write FProgress; end;
procedure TVentilThread.Execute; begin FPosition := 0;
While FPosition < Length(FOpenCloseTimes) do Begin if (FPosition and 1) = 0 Then ThreadReleaseSteam else ThreadWithholdSteam;
Sleep(FOpenCloseTimes[FPosition]);
Inc(FPosition); Synchronize(SyncShowProgress);
if ((FPosition and 1) = 0) and Terminated Then break; end; end;
procedure TVentilThread.SyncShowProgress; begin If Assigned(FOnProgress) Then FOnProgress(FPosition); end; |
Und was war jetzt so schwer an dieser Kurzdarstellung?
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
kalmi01
      
Beiträge: 39
|
Verfasst: Fr 21.01.11 19:36
packe Deine Aktion doch in das OnPaint, etwa so :
Form2.OnPaint := nil;
Mach_was_was_ganz_lange_dauert;
und im Form2.OnShow :
Form2.OnPaint := MeinOnPaint;
Oder eventuell sogar noch etwas später.
Ich weiss jetzt aus dem Stehgreif nicht, welche Ereignisse auf das OnPaint noch folgen.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Fr 21.01.11 19:43
kalmi01 hat folgendes geschrieben : | packe Deine Aktion doch in das OnPaint, etwa so :
Form2.OnPaint := nil;
Mach_was_was_ganz_lange_dauert;
und im Form2.OnShow :
Form2.OnPaint := MeinOnPaint;
Oder eventuell sogar noch etwas später.
Ich weiss jetzt aus dem Stehgreif nicht, welche Ereignisse auf das OnPaint noch folgen. |
Darf ich um die eine Einführung eines "Bitte Prügeln"-Buttons bitten?
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
Für diesen Beitrag haben gedankt: Martok
|
|
jaenicke
      
Beiträge: 19346
Erhaltene Danke: 1754
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Fr 21.01.11 19:59
BenBE hat folgendes geschrieben : | | Darf ich um die eine Einführung eines "Bitte Prügeln"-Buttons bitten? |
Also ich bin ja sonst sehr zurückhaltend was solche Forderungen angeht. Aber hier kann ich nur voll und ganz zustimmen... So einen Blödsinn habe ich lange nicht mehr gesehen...
Zur Sache:
Wie es ohne Threads geht, hatte ich ja schon gepostet. Da braucht man keine Events missbrauchen und dadurch Code an Stellen packen, wo er nichts zu suchen hat (nämlich in die reine GUI zur Fortschrittsanzeige). Das ist zwar keine optimale Lösung, aber durchaus brauchbar.
Und mit Threads mag es etwas mehr Aufwand sein, aber dafür läuft es auch deutlich besser.
Warum dann aber zusätzlich irgendwelche abstrusen Konstrukte gepostet werden (OnActivate war ja schon sone Sache, aber OnPaint schlägt der IDE den Cursor in die Titelleiste...), verstehe ich wirklich nicht.
Für diesen Beitrag haben gedankt: BenBE, Martok
|
|
Kay E. 
      
Beiträge: 118
|
Verfasst: Mo 24.01.11 18:18
So, danke nochmal für eure Antworten. Hat jetzt ein wenig gedauert, bis ich geantwortet hab, aber Wochenende und Arbeit verträgt sich nicht so gut
Wir haben uns in der Abteilung jetzt auf die Threads geeinigt, auch wenn mein Kollege nicht so begeistert davon war - ich konnte ihn überzeugen.
Die Delay-Funktion sieht interessant aus und ich habe sie mal meinem Mitbewohner (Informatiker im Endstatdium) gezeigt. Er meinte, er würde sich den Win32API - Aufruf, so wie er da steht, in einem Programm nicht trauen.
Langer Rede kurzer Sinn: Threads müssen her. Um ehrlich zu sein, die Darstellung von BenBE hat mir nicht sehr geholfen. Die grobe Ableitung von Threads ist mir klar, aber gerade das sicherstellen von Schreibrechten auf eine Variable, das Koordinieren der zwei Threads, Exceptionbehandlung auf Threadebene etc. damit hab ich noch ein Problem.
Kennt ihr da nicht ein paar gute Guides, in die ich mich einlesen könnte? Wäre sehr hilfreich!
Viele Grüße
Kay
|
|
jaenicke
      
Beiträge: 19346
Erhaltene Danke: 1754
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mo 24.01.11 18:52
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mo 24.01.11 19:13
Das Beispiel, wie ich es oben gepostet habe, ist zwar vereinfacht, funktioniert aber Problemlos, wenn keine der aufgerufenen Funktionen Exceptions liefert, also alle abgefangen werden. Auch funktioniert das so unter der Annahme, dass ALLE Resourcen, die der Thread braucht (also die COM-Port-Komponente, ...) dem Thread als Private-Properties gehören und von außen nicht manipuliert werden. Sicher, ich hab da etwas stark vereinfacht, wollte aber auch nur erstmal den Grundaufbau zeigen. Im Zweifelsfalle würde man innerhalb der While-Schleife noch mit einem zusätzlichen Try...Except arbeiten, falls man ansonsten irgendwo was vergisst abzuarbeiten.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Kay E. 
      
Beiträge: 118
|
Verfasst: Do 27.01.11 18:19
Ich hab mich durch das Tutorial, was jaenicke gepostet hat (vielen Dank an der Stelle), durchgekämpft und das Programm die letzten zwei Tage auf Thread umgestellt.
Den Thread hab ich auf anraten meines Mitbewohners autonom eingerichtet, d.h. er muss keine Informationen von außen einholen, um zu arbeiten. Er bekommt alles von Anfang an mit. Mit dem Hauptthread kommuniziert er über Messages.
Jetzt zum Problem: Die IDE zeigt keinen Fehler im Code mehr an, nur wenn ich compilieren will, schnellt die RAM-Auslatung plötzlich auf jenseits der 2GB hoch (sonst etwa 40MB) und ich handel mir den F2046: Out of Memory - Fehler ein. Woher kommt das? da ich wirklich gar keinen Schimmer hab, an was das liegen kann, tu ich mir schwer, Codebrocken mitzuliefern. Wenn ihr mir sagt, was ihr zur Analyse braucht, stell ich das gerne rein.
Ich kann soweit nur sagen, dass der Compiler bei der Fehlermeldung auf die Einbindung der Threadunit springt...
Grüße Kay
|
|
|