Custom Events und Timer Events in Konkurrenz

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

Custom Events und Timer Events in Konkurrenz

Beitrag von losgehts »

Hallo,

das unten angehängte Beispielprogramm zeigt folgendes Verhalten :
a) Nach dem Start des Programms wird ein Window-Timer wirksam, der alle 333ms eine Ausgabe in die Liste macht.
b) Nach Klick des Buttons werden 100 einfache Threads erzeugt, die gleich nach Create und kurz vor Ende je eine Ausgabe in dieselbe Liste machen.

Für sich betrachtet funktionieren a und b.

Während b allerdings aktiv ist (also noch Threads laufen), werden fast immer keine Ausgaben von a) mehr gemacht. Gelegentlich, wirklich selten, kommt noch eine timer-Ausgabe.

Warum ist das so bzw was mache ich falsch?

Das Programm ist nur unter Windows getestet.
Auf sehr schnellen Rechnern tritt das Phänomen ggf nur auf, wenn der Wert bei "threads(i)\n=1000" etwas erhöht wird.

Losgelöst vom oben beschriebenen Problem habe ich zum Modul auch noch eine Frage:
FreeStructure für die Eventdaten ist in der Behandlungsroutine AddEvent().
Wenn Events verloren gehen, findet das nicht statt.
Wie macht man das besser?

Code: Alles auswählen

; Module ProtList

EnableExplicit

; -------------------------------------------------------------------------------------------------------

DeclareModule ProtList
  
  Declare Create(Gadget.i, x.i, y.i, Width.i, Height.i, EventID_Add.i)
  Declare Free(Gadget.i)
  Declare Prot(gadget.i, z.s)
  Declare ProtE(Gadget.i, z.s)
  Declare SizeTime(gadget.i)
  
EndDeclareModule

; -------------------------------------------------------

Module ProtList
  
  EnableExplicit
	
  Structure TProtList
    Gadget.i
    EventID_Add.i
    startzeit.i
  EndStructure
  
  Structure TEventAdd
    Gadget.i
    line.s
  EndStructure
  
  ; -------------------------------------------------------
  
  Procedure.s mtt(num.q)
    Protected tmp$
    Protected.s ms, sec
    
    ms=RSet(Str(num%1000),3,"0")
    sec=RSet(Str(num/1000),6,"0")
    
    tmp$=RSet(Str(num/3481000),2,"0")+":"
    num%3481000
    tmp$+RSet(Str(num/59000),2,"0")+":"
    num%59000
    tmp$+RSet(Str(num/1000),2,"0")
    tmp$+"."+RSet(Str(num),3,"0")
    
    ProcedureReturn sec+"."+ms
  EndProcedure

  Procedure.s GetZeit(*data.TProtList, zeit.i)
    Define.q vergangen
    vergangen=zeit-*data\startzeit
    ProcedureReturn mtt(vergangen)
  EndProcedure

  
  ; -------------------------------------------------------
  
  Procedure Prot(gadget.i, z.s)
    Protected *data.TProtList
    *data = AllocateStructure(TProtList)
    With *data
      *data=GetGadgetData(gadget)
      AddGadgetItem(\Gadget,CountGadgetItems(\Gadget),GetZeit(*data,ElapsedMilliseconds()) + Chr(10) + z)
      SetGadgetState(\Gadget,CountGadgetItems(\Gadget)-1)
      SetGadgetItemColor(\Gadget,CountGadgetItems(\Gadget)-1,#PB_Gadget_BackColor,RGB(255,250,205),0)
    EndWith
  EndProcedure
  
  Procedure ProtE(Gadget.i, z.s)
    Protected *data.TProtList
    Protected *li.TEventAdd
    *data=GetGadgetData(Gadget)
    *li.TEventAdd=AllocateStructure(TEventAdd)
    *li\line=z
    *li\Gadget=Gadget
    PostEvent(*data\EventID_Add,0,0,*data\EventID_Add,*li)  
  EndProcedure
  
  
  Procedure AddEvent()
    Protected *erg.TEventAdd
    *erg=EventData()
    Prot(*erg\Gadget,*erg\line)
    FreeStructure(*erg)  
  EndProcedure
  
  Procedure.i Create(Gadget.i, x.i, y.i, Width.i, Height.i, EventID_Add.i)
    
    Protected result.i, *data.TProtList

    *data = AllocateStructure(TProtList)
    
    With *data
      If gadget=#PB_Any
        result=ListIconGadget(#PB_Any, x, y, Width, Height,"Zeit",40, #PB_ListIcon_FullRowSelect + #PB_ListIcon_AlwaysShowSelection)
        \Gadget=result
      Else
        result=ListIconGadget(Gadget, x, y, Width, Height,"Zeit",40, #PB_ListIcon_FullRowSelect + #PB_ListIcon_AlwaysShowSelection)
        \Gadget=Gadget
      EndIf
      AddGadgetColumn(\Gadget,1,"Text",1000)
      \EventID_Add=EventID_Add
      BindEvent(\EventID_Add,@AddEvent())
      \startzeit=ElapsedMilliseconds()
      SetGadgetData(\Gadget,*data)
    EndWith
    
    ProcedureReturn result
  EndProcedure
  
  Procedure Free(Gadget.i)
    Protected *data.TProtList
    With *data
      *data = GetGadgetData(Gadget)
      If *data And *data\Gadget = Gadget
        FreeStructure(*data)
      EndIf
      FreeGadget(Gadget)
    EndWith
  EndProcedure
  
  Procedure SizeTime(gadget.i)
    SendMessage_(GadgetID(gadget), #LVM_SETCOLUMNWIDTH,0,#LVSCW_AUTOSIZE_USEHEADER)
  EndProcedure
  
  
EndModule

; *****************************************************************************
; *****************************************************************************
; zum Test ...
; *****************************************************************************
; *****************************************************************************

CompilerIf #PB_Compiler_IsMainFile
  
  Enumeration
    #Main
    #MyList
    #MyButton
    #MyTimer
  EndEnumeration
  
  Enumeration #PB_Event_FirstCustomValue
    #ProtList_Add
  EndEnumeration
  
  Structure TThreadData
    ti.i
    n.i
    erg.i
    laufzeit.i
  EndStructure
    
  Procedure threadTest(*data.TThreadData)
    Protected.i i,j
    Protected.i sum=0
    *data\laufzeit=ElapsedMilliseconds()
    ProtList::ProtE(#MyList,"Thread"+Str(*data\ti)+" : "+"Start")
    For i=1 To *data\n
      For j=1 To *data\n
       sum+i+j
      Next
    Next
    *data\erg=sum
    *data\laufzeit=ElapsedMilliseconds()-*data\laufzeit
    ProtList::ProtE(#MyList,"Thread"+Str(*data\ti)+" : "+"Ende")
  EndProcedure


  Procedure Main()
    Protected.i Event, i
    Protected Dim threads.TThreadData(100)
    
    If OpenWindow(#Main, #PB_Ignore, #PB_Ignore, 700, 700, "Test" , #PB_Window_SystemMenu)
      
      ProtList::Create(#MyList , 10, 10, 600, 600,#ProtList_Add)
      ButtonGadget(#MyButton,620,100,50,50,"Go")
      
      ProtList::Prot(#MyList,"Start")
      ProtList::SizeTime(#MyList)
      
      AddWindowTimer(#MAIN,#MyTimer,333)
      
      Repeat
        Event=WaitWindowEvent()
        Select Event
          Case #PB_Event_CloseWindow
            Break
          Case #PB_Event_Gadget
            Select EventGadget()
              Case #MyButton
                For i=1 To 100
                  threads(i)\n=1000
                  threads(i)\ti=i
                  CreateThread(@threadTest(),@threads(i))
                Next
                
            EndSelect
          Case #PB_Event_Timer
            If EventTimer()=#MyTimer
              ProtList::ProtE(#MyList,"-------------- timer event ---------------")
            EndIf
            
        EndSelect
      ForEver
      
      ProtList::Free(#MyList)
      
    EndIf
    
  EndProcedure 
  
  Main()
  
CompilerEndIf
Axolotl
Beiträge: 266
Registriert: 31.12.2008 16:34

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von Axolotl »

Ich habe dein Programm nicht ausprobiert.
Es ist aber wohl so, dass Timer Events (#WM_TIMER, oder #PB_Event_Timer) eine niedrige Priorität gegenüber anderen Nachrichten und darüber hinaus eine besondere Behandlung innerhalb der Nachrichtenschlange (Message Queue) haben..
Sie werden eigentlich nur bearbeitet (in die Queue eingetragen) wenn window mehr oder weniger im Idle Modus ist.

Einfaches Beispiel

Code: Alles auswählen

Define timercount, mousemovecount, bShow = 1

If OpenWindow(0, 0, 0, 300, 300, "Prio of the Timer Messages", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CheckBoxGadget(1, 8, 0, 80, 20, "Update List ")
  ListViewGadget(0, 8, 24, 284, 284, $4000) 

  AddWindowTimer(0, 0, 100)  ; Timeout = 100 ms
  SetGadgetState(1, bShow) 

  Repeat
    event = WaitWindowEvent() 
    Select event ; WaitWindowEvent() 
      Case #PB_Event_CloseWindow 
        Break 

      Case #PB_Event_Gadget 
        Select EventGadget() 
          Case 1 
            bShow ! 1 

        EndSelect 

      Case #PB_Event_Timer ; Each 10 ms => Let's display the coordinates
        Select EventTimer() 
          Case 0 
            timercount + 1 

            If bShow 
              AddGadgetItem(0, -1, "Timer " + timercount) 
              SetGadgetState(0, CountGadgetItems(0)-1) 
            EndIf 
        
        EndSelect 

      Case #WM_MOUSEMOVE 
        mousemovecount + 1 
        If bShow 
          AddGadgetItem(0, -1, "MouseMove " + mousemovecount) 
          SetGadgetState(0, CountGadgetItems(0)-1) 
        EndIf 

    EndSelect 
  ForEver 
EndIf
Using PureBasic latest stable version and current alpha/beta (x64) on Windows 11 Home
losgehts
Beiträge: 16
Registriert: 21.06.2020 12:31

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von losgehts »

Danke für den Hinweis, damit konnte ich was anfangen.

Habe nun testweise den Timer simuliert mittels weiterem Thread, der in Endlosschleife mit Delay PostEvents macht.
Dem habe ich Priority 31 gegeben.
Dann sieht man, dass die simulierten Timer-Ausgaben während des Starts der 100 Threads zwar auch nicht live angezeigt werden, aber das liegt daran, dass innerhalb der Start-Schleife ja kein Eventhandling ist.
Die simulierten Timer-Events werden aber nachgeholt hinsichtlich Ausgabe, sind also wirklich da.
Die Original-Timer-Events sind dagegen einfach ausgefallen.
Benutzeravatar
HeX0R
Beiträge: 3040
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von HeX0R »

Ich würde der CPU etwas Zeit zum Atmen einräumen, davon abgesehen hast Du eh keine CPU mit 100 Cores, daher macht das Beispiel auch wenig Sinn, die arbeiten ja alle unter Vollast, da bleibt nicht mehr viel für den Mainthread (sieht man auch daran, dass sich das Fenster währenddessen fast nicht mehr bewegen lässt).
Ausserdem die Threads kaskadiert starten, also so z.B.:

Code: Alles auswählen

	Procedure threadTest(*data.TThreadData)
		Protected.i i, j
		Protected.i sum = 0
		
		Delay(*data\ti * 10) ;<- starte kaskadiert
		*data\laufzeit = ElapsedMilliseconds()
		ProtList::ProtE(#MyList, "Thread" + Str(*data\ti) + " : " + "Start")
		For i = 1 To *data\n
			For j = 1 To *data\n
				sum + i + j
			Next
			Delay(0)  ;<- atme
		Next
		*data\erg      = sum
		*data\laufzeit = ElapsedMilliseconds() - *data\laufzeit
		ProtList::ProtE(#MyList, "Thread" + Str(*data\ti) + " : " + "Ende")
	EndProcedure
losgehts
Beiträge: 16
Registriert: 21.06.2020 12:31

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von losgehts »

Hallo Hex0R,

danke für Deine Hinweise!

Ich wollte mit dem Beispiel prüfen, ob der Timer auch im Extremfall noch regelmäßig zum Zuge kommt. Meinetwegen langsam, weil das System insgesamt total überlastet ist, aber doch regelmäßig. Dafür musste ich dann halt den Extremfall erzeugen.

Zum Delay :
Ich habe es bisher so verstanden, dass das Delay (auch bei 0) dem OS die Möglichkeit gibt, auf einen anderen Thread umzuschalten, falls einer da ist. Das muss aber nicht der Mainthread sein. Das würde bedeuten, dass in meinem Beispiel bei Delay(0) der Mainthread nur in 1/101 der Fälle kurz dran kommt, vielleicht sogar seltener, weil die 100 erzeugten Threads "fordernder" sind.
Oder gibt Delay garantiert an den Mainthread ab?
Benutzeravatar
HeX0R
Beiträge: 3040
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von HeX0R »

Mir ist schon klar, dass das nur ein Beispiel war, aber selbst hier sollte man sich über die Sinnhaftigkeit Gedanken machen.
Gehen wir mal davon aus, Du hast wirklich einen Use-Case mit 100 Aufgaben, bei der jeder einen Core zu 100% auslasten würde.
Dann macht es keinen Sinn, diese 100 Aufgaben auf jeweils einen Thread auszulagern.
Weil, wie gesagt, Deine CPU keine 100 Cores hat, Du würdest absolut nichts dadurch gewinnen, außer, dass das ganze Programm nahezu unbedienbar wird.
Diese 100 Aufgaben können gar nicht parallel abgearbeitet werden, und die Threads bremsen sich gegenseitig noch aus.
Man versucht hier die Aufgaben auf die verschiedenen vorhandenen Cores zu verteilen und dann entsprechend viele Aufgaben dort hintereinander auszuführen.
Und bei vielen parallelen Threads würde ich immer kleine Delays einbauen, wir sind in einer Multitask-Umgebung, und man sollte auch immer Rücksicht auf andere (nicht unsere) Tasks nehmen.
Wenn ich ein Programm starte, das meine komplette CPU zum kochen bringt und alles andere unbedienbar ist, wird das Ruckzuck in die Tonne gekickt.
Klar, das war hier nur ein Test und auch nur für Dich, aber wieso sollte man völlig Abseits der Realität testen?

Habe ich nur einen parallelen Thread, kann der auch ruhig einen Core vollständig braten, dann spare ich mir gerne die Delays.
Axolotl
Beiträge: 266
Registriert: 31.12.2008 16:34

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von Axolotl »

Zum Thema Windows und Timer haben wir hier bisher nur die Oberfläche angekratzt.
Timer zu simulieren ist nicht so einfach bzw. nahezu unmöglich. Wie schon geschrieben werden die vom System sehr speziell behandelt.
Wichtig zu wissen ist das diese Timer auch nicht besonders genau sind.
Wenn Du spezielle Dinge mit Timern vorhast, dann solltest du mal diesen englischen Artikel lesen.
Der beschreibt ganz gut die unterschiedlichen Timer (Multimedia Timer, Waitable Timer, Queue Timer), die es unter Windows so gibt.
Allerdings dürfte die Verwendung mit PB nur mit Hilfe der API möglich sein, aber vielleicht gibt es da ja schon was in den Foren ......
Using PureBasic latest stable version and current alpha/beta (x64) on Windows 11 Home
losgehts
Beiträge: 16
Registriert: 21.06.2020 12:31

Re: Custom Events und Timer Events in Konkurrenz

Beitrag von losgehts »

Danke für den Artikel, sehr interessant!

Mein Anlass für das Timer-Experiment war :
Hatte in meinem eigentlichen Programm (das Beispiel oben ist stark verkürzt für die Detailfrage) festgestellt, dass das Programm "zäh" wird, wenn meine Threads zu viel machen und/oder das Gesamtsystem zu viel macht. Daher wollte ich einen Überwachungsthread hinzufügen, der "immer mal wieder" prüft, ob was am Verhalten der anderen Threads zu ändern ist. Der Überwachungsthread (so der spontane Gedanke) sollte dann durch ein Timer-Event getriggert werden.

Dafür sind Timer aber nicht geeignet, wie ich nun gelernt habe.

Habe den Überwachungsthread nun ohne Trigger (also mit Endlosschleife [außer schlüssigem Abbruch]) eingebaut und lasse ihn per Delay (s.o., Hexor) pausieren.
Antworten