Text live ausgeben innerhalb längerer Berechnungen

Anfängerfragen zum Programmieren mit PureBasic.
losgehts
Beiträge: 16
Registriert: 21.06.2020 12:31

Text live ausgeben innerhalb längerer Berechnungen

Beitrag von losgehts »

Beschreibung :

Es kommt öfter mal vor, dass ich kleine PB-Programme schreibe, die irgendwas simulieren, dafür sehr lange rechnen und zwischendurch mal was ausgeben sollen, gerne in ListViews. Dafür hänge ich unten ein (inhaltlich sinnloses) Beispiel an.

Nun ist es so, das derartige Ausgaben mitten in der Berechnung erst am Ende gebündelt angezeigt werden, was sicher daran liegt, dass die Berechnung das Eventhandling blockiert. Das könnte man sicher umgehen, indem man die Berechnung selbst als Thread startet und die gewollten Ausgaben als Event an das Hauptprogramm gibt.
Was aber grundsätzlich auch funktioniert (jedenfalls meistens), sind die beiden Zeilen, die ich unten in Prot eingefügt habe (Delay + WindowEvent).
Klappt (bei mir) nicht zuverlässig mit Delay(1), aber Delay(10) geht meistens.
Ich verstehe auch nicht, warum das funktioniert. Delay (das ja eine Wirkung hat) alleine geht nicht, und mit dem WindowEvent mache ich ja gar nichts.

Also meine Fragen :
Warum bewirkt Delay / WindowEvent kombiniert, dass die beiden Ausgaben im Beispiel live kommen, und nicht die erste zeitverzögert?
Wie macht man das besser?

Hier das Beispiel :

Code: Alles auswählen


EnableExplicit

Enumeration
  #WinMain
  #GAD_List
  #GAD_Button
EndEnumeration


Procedure Prot(s.s)
  Protected.i event
  AddGadgetItem(#GAD_List,-1,s)
  SetGadgetState(#GAD_List,CountGadgetItems(#GAD_List)-1) 
  ;redraw ermöglichen :
  Delay(10)
  event=WindowEvent()
  
EndProcedure

Procedure.i Doit()
  Protected.i i,n,zeit, tempzeit
  Protected.i event
  Dim dummy.i(100)
  
  ;ausgabe 1
  Prot("los gehts")
    
  ;zeit verbrauchen
  zeit=0
  n=0
  While zeit<3000
    tempzeit=ElapsedMilliseconds()
    For i=1 To 10000
      RandomizeArray(dummy())
    Next
    n=n+10000
    zeit=zeit+ElapsedMilliseconds()-tempzeit
  Wend
  
  ;ausgabe 2
  Prot("n="+Str(n))
  
  
EndProcedure

If OpenWindow(#WinMain, 1000, 0, 1000, 1000, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  ListViewGadget(#GAD_List,10,10,900,900)
  ButtonGadget(#GAD_Button,10,910,150,50,"Go!")
  
  Define.i Event
  Repeat
    Event=WindowEvent()
    Select Event
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #GAD_Button  : Doit()
        EndSelect
    EndSelect
    Delay(1)
  Until Event = #PB_Event_CloseWindow
  
EndIf

Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von STARGÅTE »

Mal abgesehen davon, dass ich nicht weiß was du mit "dass die beiden Ausgaben im Beispiel live kommen" meinst, ist diese Art des Events-Handlings nicht zu empfehlen.
Man sollte (oder schärfer darf) immer nur eine Event-Schleife haben und dort logischerweise auch nur ein einziges WindowEvent(). Durch den weiteren Aufruft von WindowEvent() in deiner Prot() Prozedur verschluckst du möglicherweise wichtige Events, die du unten im Select-Block abfragen wolltest. Von dieser Art der Programmierung ist abzuraten.
Außerdem sollten Delays nie in jedem Iterationsschritt aufgerufen werden, sondern nur dann, wenn es keine Events gab (#PB_Event_None). Ansonsten könnten die Events sich stauen, wenn mehr erzeugt werden als abgearbeitet werden.

Möglicher Lösungsweg ohne Threads:
Beim drücken des Buttons wird Prot() aufgerufen und ein Custom Event erzeugt ( PostEvent() ), welches dann von WindowEvent() verarbeitet wird und dann erst die eigentliche Berechnung startet. So wird das AddGadgetItem erst ordnungsgemäß abgearbeitet und dann die Berechnung gestartet.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von mk-soft »

Als erstes NIE ZWEI MAL WindowEvent() verwenden, sonst gehen dir Ereignisse verloren.
Immer WaitWindowEvent im Haupt Loop verwenden und niemals im EventLoop Delay verwenden, sonst kann es zum "Programm reagiert nicht" kommen.

Nebenbei:
Die Funktion WindowEvent wird für Games mit OpenWindowedScreen benötigt um den Event Buffer zu leeren und Abzufragen.

Berechnung in Threads auslagern und mit PostEvent zum Hauptprogramm Zwischen-Ergebnisse senden (aber nicht zu viele)

Beispiele mit Threads:
Link: Mini Thread Control. Siehe auch englische Version mit mehr Beispiele
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
losgehts
Beiträge: 16
Registriert: 21.06.2020 12:31

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von losgehts »

Danke Euch!
Mal abgesehen davon, dass ich nicht weiß was du mit "dass die beiden Ausgaben im Beispiel live kommen" meinst
Wenn ich Delay/WinEvent drin lasse, kommt die erste Ausgabe praktisch sofort (nannte ich live).
Wenn ich es auskommentiere, kommen beide Ausgaben erst am Ende.



Ich hatte die Frage auch mal dem ChatGPT gestellt. Der meinte, ich möge PeekEvent() verwenden, weil der die Queue nur liest und nicht verändert, so die KI. Hat mir sogar ein Beispiel geliefert. Problem war nur, dass PB kein PeekEvent() kennt. KI hat sich auf den Hinweis hin entschuldigt.
Ich hatte aber gehofft, dass hier jemand etwas Vergleichbares kennt. Zumal ich (bin fast sicher) sowas früher in .net mit Application.DoEvents() bzw mit Application.ProcessMessages() in Delphi gemacht habe. Kann natürlich auch falsch gewesen sein.

Ok, es führt also kein Weg am Custom Event vorbei.

Danke für den Hinweis mit WaitWinEvent(). Ich hatte bisher die nebulöse Sorge, dass gerade das Wait zu Hängern führt, aber eigentlich ists schlüssig, dass es nicht so ist.

Noch nicht klar ist mir allerdings, warum mein schmutziger Workaround überhaupt funktioniert, bzw. ich habe es nicht verstanden, wenn Eure Texte diese Frage beantwortet haben.
Lord
Beiträge: 324
Registriert: 21.01.2008 19:11

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von Lord »

Ich weiß zwar auch nicht, was Dein Beispiel demonstrieren soll, aber hier
mal Dein Beispiel umgeschrieben mit einer Eventschleife und einem Thread:

Code: Alles auswählen

EnableExplicit

Enumeration
  #WinMain
  #GAD_List
  #GAD_Button
EndEnumeration
Enumeration #PB_Event_FirstCustomValue 
  #CustomEvent1
  #CustomEvent2
EndEnumeration

Define myThread, egal

Procedure Prot(s.s)
  AddGadgetItem(#GAD_List,-1, s)
  SetGadgetState(#GAD_List,CountGadgetItems(#GAD_List)-1) 
EndProcedure

Procedure Doit(egal)
  Protected.i i, n, zeit, tempzeit
  Dim dummy.i(100)
  
  PostEvent(#CustomEvent1)
  
  zeit=0
  n=0
  While zeit<3000
    tempzeit=ElapsedMilliseconds()
    For i=1 To 10000
      RandomizeArray(dummy())
    Next
    n=n+10000
    zeit=zeit+ElapsedMilliseconds()-tempzeit
  Wend
  
  PostEvent(#CustomEvent2, #WinMain, 0, #CustomEvent2, n)
  
EndProcedure

If OpenWindow(#WinMain, 1000, 0, 1000, 1000, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  ListViewGadget(#GAD_List,10,10,900,900)
  ButtonGadget(#GAD_Button,10,910,150,50,"Go!")
  
  Define.i Event
  Repeat
    Event=WaitWindowEvent()
    Select Event
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #GAD_Button
            myThread=CreateThread(@Doit(), egal)
        EndSelect
      Case #CustomEvent1
        Prot("los gehts")
      Case #CustomEvent2
        Prot("n="+EventData())
    EndSelect
  Until Event = #PB_Event_CloseWindow
  
EndIf

Da Du die zweite Textausgabe 'Prot("n="+Str(n))' erst am Ende der Schleife
ausführst, wird sie natürlich auch erst bei Beendigung von 'DoIt()' ausgeführt und
dadurch der zweite Text erst zum Abschluß angezeigt.
Oder worum ging es hier überhaupt?
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von NicTheQuick »

Früher habe ich einfach eine While-Schleife benutzt um während solcher Berechnungen Hintergrund-Events abzuarbeiten, ohne darauf zu reagieren:

Code: Alles auswählen

While WindowEvent(): Wend
Das hat gereicht um ein ListViewGadget() oder ähnliche Gadgets, die Events auslösen, wenn man sie ändert, und neu gezeichnet werden zu müssen, live zu aktualisieren.

Aber schön ist das natürlich nicht. Weil der User sich dann wundert, wenn er z.B. auf X klickt und nichts passiert, solange die Berechnung aktiv ist.
losgehts
Beiträge: 16
Registriert: 21.06.2020 12:31

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von losgehts »

Danke, Lord!

Zuletzt ging es nur noch darum, warum sich das Programm mit den Zeilen Delay und WindowEvent innerhalb Prot überhaupt anders verhält als ohne die Zeilen.
Denn ich verwende das dort abgerufene Event ja gar nicht.
"Anders verhalten" bedeutet:
Mit den Zeilen kommt die 1. Ausgabe ca. 3 Sekunden vor der 2. Ausgabe.
Ohne die Zeilen kommen beide Ausgaben quasi gleichzeitig, nämlich ca. 3 Sekunden nach dem Buttonclick.


Ich vermute, dass der Aufruf von WindowEvent() mehr macht als nur ein Event aus der Queue zu holen.
Bei manchen Events (z.B. ein Redraw für ListView) scheint auch gleich ein in (in der IDE nicht sichtbarer) Code zum Eventhandling aufgerufen zu werden. Das Event wird gar nicht von der Funktion zurück gegeben, es wird einfach nur verarbeitet.

Ich reduziere das Beispiel mal :

Code: Alles auswählen

EnableExplicit

Enumeration
  #WinMain
  #GAD_List
EndEnumeration


If OpenWindow(#WinMain, 1000, 0, 1000, 1000, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  ListViewGadget(#GAD_List,10,10,900,900)
  
  AddGadgetItem(#GAD_List,-1,"Hallo")
    
  Define.i Event
  Repeat
    Event=WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
  
EndIf

Ich behandle kein Redraw-Event, trotzdem findet Redraw statt.
Muss also irgendwie intern sein und vom WaitWindowEvent() ausgelöst werden.
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von mk-soft »

Es muss immer WaitWindowEvent() in einen kontinurlichen Loop aufgerufen werden, sonst wird nichts was mit der GUI zu tun hat abgearbeitet.
Diese ist auch die Schnittstelle zu deinem Programm um auf Benutzer Aktionen zu reagieren und alle GUI Objekte zu aktuallisieren.

Also nie ein Delay verwenden.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Text live ausgeben innerhalb längerer Berechnungen

Beitrag von NicTheQuick »

losgehts hat geschrieben: 16.02.2023 13:53Ich vermute, dass der Aufruf von WindowEvent() mehr macht als nur ein Event aus der Queue zu holen.
Bei manchen Events (z.B. ein Redraw für ListView) scheint auch gleich ein in (in der IDE nicht sichtbarer) Code zum Eventhandling aufgerufen zu werden. Das Event wird gar nicht von der Funktion zurück gegeben, es wird einfach nur verarbeitet.
Damit liegst du fast richtig. Solange WindowEvent() nicht 0 zurück gibt, gab es irgendeinen Event. Du musst aber ja nicht auf alle Events reagieren. Dich interessiert es zum Beispiel selten, wenn der Nutzer seine Maus über dem Fenster bewegt. Interessant wird erst, wenn er irgendwo drauf klickt, was etwas sinnvolles tut. Aber die Möglichkeit besteht, dass du auf all das reagieren kannst. Noch mehr Events gibt es nur noch, wenn du WindowCallbacks nutzt, die es aber meines Wissens nur in der Windows-Version gibt.

Die ganze Eventverarbeitung tut noch ganz anderen Kram im Hintergrund, der aber durch Events ausgelöst wurde. Rufst du (Wait)WIndowEvent() aber nicht regelmäßig auf, kommt es sozusagen zu einem Event-Stau und bestimmte Dinge passieren dann einfach nicht.
Antworten