Thread() & Signal()

Klassen, Objekte, Methoden, Instanzen

Moderator: Moderatoren

Antworten
Benutzeravatar
AUGE_OHR
Marvin
Marvin
Beiträge: 12903
Registriert: Do, 16. Mär 2006 7:55
Wohnort: Hamburg
Hat sich bedankt: 19 Mal
Danksagung erhalten: 44 Mal

Thread() & Signal()

Beitrag von AUGE_OHR »

hi,

D:\ALASKA\XPPW32\SOURCE\samples\basics\THREAD\Coffee.prg

also es geht um die Verwendung von Multi Threads & Signal.

Ich hab statt XbpDialog als Demo XbpCrt gemacht und nun Probleme
das in meine Hybrid Application einzubaun. Die Thread scheinen alle
soweit zu laufen und die SetAppWindow()/SetAppFocus() stimmt "im
Prinzip" d.h. "alte Fenster" bleiben erhalten wenn die den "Focus" verlieren

Nun kann es aber sein das ich eine "lange Ausgabe" habe und die noch
nicht "stable" ist wenn ich in ein "anderes Fenster" springen will ...

also denke ich mir muss ich wohl Signal() verwenden um den einzeln
Thread() mitzuteilen ob er "darf" oder nicht.

Die "Grundidee" ist nun die "Kaffeemachine" welche nur eine "capacity"
von exakt 1 Tasse "Kaffee" hat. Nur der "Programmer" der zur der
"Kaffeemachine" geht ( :Enter ) kann sich 1 Tasse "Coffee" holen
( :takecoffee ) und dann arbeiten.

also "entziehe" ich allen "Programmern" der "Coffee" und gebe nur dem
"Fenster" was aktive ist 1 Tasse "Coffee" womit dann die "Kaffeemachine"
leer sein sollte.

das dumme scheint nun bei "langen Ausgaben" zu sein, den trotz "Coffee"
= 0 arbeitet der "Programmer" weiter an der "langen Ausgabe" um erst
dann zu reagieren ... ?!

ich müsste also irgendwie noch raus bekommen ob der "Programmer"
noch an einer "langen Ausgabe" ist ober ob er das :wait() kapiert hat.

also wie bekomme das raus und wie kann ich "lange Ausgaben"
entsprechend anpassen ?

Code: Alles auswählen

ohne weiter Sinn

PROCEDURE FILLSCR
LOCAL x,y
   CLS
   FOR x = 1 TO MAXROW()
      FOR y = 1 TO MAXCOL()
         @ x,y say "*"
         INKEY(0.001)
      NEXT
   NEXT
RETURN
gruss by OHR
Jimmy

Code: Alles auswählen

procedure appsys()
return

PROCEDURE Main
   LOCAL i, oMachine
   LOCAL aTime := { 130, 100, 140, 90, 100, 110, 150, 120, 100 , 1000}
   LOCAL aNames:= { "Martin  ", "Hubert  ", ; // nach Forum "Beitr„ge"
                    "Manfred ", "Tom     ", ;
                    "Jan     ", "Andreas ", ;
                    "Olaf    ", "Wolfgang", ;
                    "Marcus  ", "Jimmy   "  }

   LOCAL aPosi := { {000,000+50}, {330,000+50}, ;
                    {000,075+50}, {330,075+50}, ;
                    {000,150+50}, {330,150+50}, ;
                    {000,225+50}, {330,225+50}, ;
                    {000,300+50}, {330,300+50}  }

   local opar:=xbpDialog():new(setappwindow(),,{0,0},{800,600},,.t.)
   opar:create()

   oMachine := KaffeeMachine():new( 20 , opar:drawingarea )
   oMachine:start()

   FOR i := 1 TO 10
      Programmer():new( aNames[i], aTime[i], oMachine, opar:drawingarea, aPosi[i] ):start()
   NEXT

   SETAPPFOCUS(opar)

   Inkey(0)
RETURN

CLASS  KaffeeMachine FROM Thread
   EXPORTED:
   VAR capacity, Kaffee, waterIsFilled, KaffeeIsReady, ocrt1
   METHOD init, execute
   SYNC METHOD enter
ENDCLASS

METHOD KaffeeMachine:init( nCapacity, opar)
   ::thread:init()
   ::setInterval( 10 )
   ::Kaffee        := 0
   ::capacity      := nCapacity
   ::waterIsFilled := Signal():new()
   ::KaffeeIsReady := Signal():new()
   ::ocrt1         := xbpcrt():new(opar,,{ 0, 500}, 4,80):create()
RETURN self

METHOD KaffeeMachine:enter( oProgrammer )
   oProgrammer:takeCoffee()
RETURN self

METHOD KaffeeMachine:execute
   LOCAL n

   IF ::Kaffee < 1
      SETAPPFOCUS(::ocrt1)
      SETAPPWINDOW(::ocrt1)
      DispoutAt( 1, 0, PadR( "Kaffee ist alle! ",80 ), "W+/B" )

      ::waterIsFilled:wait()

      n := 0
      DO WHILE ++n <= ::capacity
         SETAPPWINDOW(::ocrt1)
         DispoutAt( 1, 0, PadR( "Kaffeemaschine l„uft! "+Replicate(Chr(219),2*n), 80 ), "W+/B" )
         Sleep(35)
      ENDDO
      ::Kaffee := ::capacity
   ENDIF

   SETAPPWINDOW(::ocrt1)
   DispoutAt( 1, 0, PadR( "Kaffee ist fertig!    "+Replicate(Chr(176),::Kaffee*2), 80 ) )
   DispoutAt( 2, 0, PadR( "", 80 ), "W+/N" )

   ::KaffeeIsReady:signal()

RETURN self

CLASS  Programmer FROM Thread
   EXPORTED:
   VAR name, coffee, coffeeMachine, ocrt2
   METHOD init, execute, takeCoffee
ENDCLASS

METHOD Programmer:init( cName, nInterval, oMachine, opar, aposi)
   ::thread:init()
   ::setInterval( nInterval )
   ::coffee        := 0
   ::name          := cName
   ::coffeeMachine := oMachine
   ::ocrt2         := xbpcrt():new(opar,,aposi,3,40,cName):create()
RETURN self

METHOD Programmer:execute
   LOCAL nRow := 1

   IF ::coffee == 0
      SETAPPFOCUS(::ocrt2)
      SETAPPWINDOW(::ocrt2)
      DispoutAt( nRow, 0, PadR( ::name + " BRAUCHT Kaffee", 80), "W+/R" )

      ::coffeeMachine:enter( self )
   ENDIF

   SETAPPWINDOW(::ocrt2)
   DispoutAt( nRow, 0, PadR( ::name + IF(UPPER(::name)="JIMMY",;
                                                " testet   - ",;
                                                " arbeitet - ");
                            +Replicate( ".", 10*::coffee), 80) )

   ::coffee := Max( ::coffee - 0.1, 0 )
RETURN

METHOD Programmer:takeCoffee
   LOCAL nRow := 1

   IF ::coffeeMachine:Kaffee < 1

      SETAPPFOCUS(::CoffeeMachine:ocrt1)
      SETAPPWINDOW(::CoffeeMachine:ocrt1)
      DispoutAt( nRow+1, 0, PadR( ::name + " KOCHT Kaffee", 80 ), "W+/B" )

      ::coffeeMachine:waterIsFilled:signal()
   ENDIF

   ::coffeeMachine:KaffeeIsReady:wait()

   SETAPPFOCUS(::ocrt2)
   SETAPPWINDOW(::ocrt2)
   DispoutAt( nRow, 0, PadR( ::name + " nimmt sich Kaffee ", 80 ), "N/G" )

   ::coffeeMachine:Kaffee -= 1

   ::coffee := 1
   Sleep(100)
RETURN self
*
* eof
*
[/b]
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15689
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Beitrag von brandelh »

Hallo Jimmy,

lass deine Threads ruhig laufen, dein Problem sind nicht die langen Ausgaben, sondern dass @ SAY den Zeiger der aktuellen Ausgabe für alle versetzt und somit keinesfalles Threadsave ist, also nicht angewendet werden darf.

Statt @ SAY mußt du

DispOutAt( <nRow>, <nCol>, <Expression>, [<cColor>], [<oWindow>] ) --> NIL

verwenden. Hierbei muss jeder Thread sich selbst die Koordinaten merken, wohin geschrieben wird.
Gruß
Hubert
Benutzeravatar
AUGE_OHR
Marvin
Marvin
Beiträge: 12903
Registriert: Do, 16. Mär 2006 7:55
Wohnort: Hamburg
Hat sich bedankt: 19 Mal
Danksagung erhalten: 44 Mal

Beitrag von AUGE_OHR »

hi,
brandelh hat geschrieben: ... sondern dass @ SAY den Zeiger der aktuellen Ausgabe für alle versetzt
und somit keinesfalles Threadsave ist, also nicht angewendet werden darf.

Statt @ SAY mußt du

DispOutAt( <nRow>, <nCol>, <Expression>, [<cColor>], [<oWindow>] ) --> NIL

verwenden. Hierbei muss jeder Thread sich selbst die Koordinaten merken, wohin geschrieben wird.
hm ja, ich hatte es auch mal mit DispOutAt() probiert, allerdings ohne
"oWindow". Da "default" SetAppWindow() benutzt wurde "sah" ich keine
unterschied zum "SAY" ...

das Demo FILLSCR() hat auch zugegeben überhaupt keine Sinn, aber
"normal" dauert eine "Ausgabe" ja auch nicht so lange und das "Fenster"
ist schon "stable" wenn ich "umschalte"

da ich nun nicht den gesamten Code umschreiben will denke ich an einen
Eingriff in die STD.CH wo ich für SAY finde :

Code: Alles auswählen

#command  @ <nRow>, <nCol> SAY <say> [COLOR <Color>] ;
      =>  DevPos(<nRow>, <nCol>) ; DevOut(<say> [,<Color>])
wie bekomme ich den da ein "oWindow" rein ?
und wenn "oWindow", wie erzähle ich im das er "das" Thread Object
als "oWindow" nehmen soll solange "der" Thread läuft ?

aber zurück zum eigendlichen Problem : wie weiss ich ob ein Thread das
:wait() "erkannt" hat. Mit Thread:active bekomme ich ja nur raus das der
Thread noch "aktive" ist nicht aber ob er "arbeitet" ?

gruss by OHR
Jimmy
Benutzeravatar
AUGE_OHR
Marvin
Marvin
Beiträge: 12903
Registriert: Do, 16. Mär 2006 7:55
Wohnort: Hamburg
Hat sich bedankt: 19 Mal
Danksagung erhalten: 44 Mal

Beitrag von AUGE_OHR »

hi,

also die GrundIdee war/ist schon richtig. Nun hab ich die "Kaffeemachine"
einfach "gestrichen" und "verteile" den "Coffee" an die "Programmer" per
Tabpage. Da ich vorher allen "Programmer" den "Coffee entzogen" habe
laufen die alle in der :execute() auf ... aber es gibt kein :takeCoffee :)

Nur dem "Programmer" dessen Tabpage per maximize() aktive ist
bekommt 1 Tasse "Coffee".

Code: Alles auswählen

   Thread A             Thread B 

   A:coffee := 1  B:Coffee := 0
   läuft                wartet
      |                    |           // von Programmcode 
      |                 B:Coffee := 0
      |                 B:wait() // Thread B anhalten 
      | 
      | Thread A führt Code aus 
      A:Coffee := 1
      A:signal()
      |
      | Thread B aufrufen und warten
      A:Coffee := 0
      A:wait()       B:Coffee := 1
      |                 B:signal() 
      |                     |
      |                 Ende B
      |                 B:Coffee := 0
      |                 B:wait()
      | 
      A:Coffee : oldCoffee
      Fortsetzung    // T
      A:signal()
      |
zu beachten ist das ich ja jederzeit vom Thread A den Thread B
aktivieren könnte ... es ist also vorher alles * abzuspeichern um
nach beenden von Thread B weiter machen zu können.
* für jeden Thread die komplette Workspace ( WorkspaceEval() )

zum "umschalten" nehme ich übrigens XbpTabpage.

Code: Alles auswählen

oXbp:TabActivate := {|| Switch2Tab(7,aTabObj,oMachine) }
....

FUNCTION  Switch2Tab(nNo,aTabObj,oMachine)
LOCAL i, iMax := LEN(aTabObj)
LOCAL aTabCrt := {}

STATIC nLast  := 0      // letzte Auswahl

IF LEN( SP_RunADD() ) < 2   // max 2 Threads
   // jedes mal neu erstellen ist immer aktuell
   //
   AADD(aTabCrt, oArtikel  )
   AADD(aTabCrt, oKunden   )
   AADD(aTabCrt, oFaktu    )
   AADD(aTabCrt, oUmsatz   )
   AADD(aTabCrt, oKreditor )
   AADD(aTabCrt, oUtil     )
   AADD(aTabCrt, oEnde     )

// ohne Kaffeemachine
*   oMachine:Kaffee        := 0                  // nichts mehr in der Kaffee

   FOR i = 1 TO iMax
      IF i = nNo
      ELSE
         aTabObj[i]:minimize()                  // Tab minimieren

         IF aTabCrt[i] <> NIL                    // wenn CRT Thread vorhanden
            aTabCrt[i]:Coffee := 0              // Coffee "entziehen"
*            aTabCrt[i]:oWait:wait()           // NICHT hier
            IF i = nLast
               aTabCrt[i]:aArea := SaveWorkSpace() // sichere Workspace
            ENDIF
         ENDIF
      ENDIF
   NEXT

   aTabObj[nNo]:maximize()                        // angeclickten Tab aktivieren
   nLast  := nNo               

   IF aTabCrt[nNo] <> NIL                           // wenn CRT exitiert
      aTabCrt[nNo]:Coffee := 1                     // 1 Tasse Coffee "geben"
*      aTabCrt[nNo]:oWait:signal()                // NICHT hier

       IF aTabCrt[nNo]:IDno = ThreadID()
*         RestWorkSpace(aTabCrt[nNo]:aArea) // NICHT hier
       ENDIF
   ENDIF

*   oMachine:Kaffee        := 1                  // exact 1 Tasse Kaffee

   DO CASE
...
zu beachten ist das man :wait() / :signal() nicht "direkt" aufrufen sollte
sondern dieses dem :execute der einzelnen Thread selbst überlässt wenn
der "Coffee" = 0 ist.

Code: Alles auswählen

METHOD CrtThread:execute
//
// umschaltung von "Fenstern" NUR durch Switch2Tab Funktion !
//
#IFDEF ORIGINAL
   IF ::coffee == 0
      ::CoffeeMachine:enter( self )
   ENDIF
   ::coffee := Max( ::coffee - 0.1, 0 )
#ELSE
   IF ::coffee == 0
      ::oWait:wait()
   ELSE
      ::oWait:signal()
   ENDIF
#ENDIF
RETURN
was ich nun noch brauche ist sowas wie den XbpGET "Controller" um die
"Getlist" pro Thread zu steuern ... schon mal jemand sowas gemacht ?

gruss by OHR
Jimmy
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15689
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Beitrag von brandelh »

Hi Jimmy,

wenn du eine lokale Variabel Getlist := {} verwendest, kann jeder Thread seine eigene haben, aber ich denke im alten CRT @ SAY etc. Modell sollte man es nicht mit verschiedenen Threads übertreiben.
Da ist viel über globale Variablen geregelt und das kann bei mehreren Threads nur Ärger geben.

Erst in der Voll-GUI ist die Trennung einzelner Fenster sauber möglich.
Gruß
Hubert
Benutzeravatar
andreas
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1902
Registriert: Mi, 28. Sep 2005 10:53
Wohnort: Osnabrück
Hat sich bedankt: 4 Mal
Kontaktdaten:

Beitrag von andreas »

Hallo Jimmy,

wenn ich mich nicht teusche, gibt es in XBase ein Befehl zur überprüfung, ob die angegebene Threads beschäftigt sind oder wartet auf die Threads, so lange die beschäftigt sind z.b. ThreadWait o. ThreadWaitAll. Mit ThreadInfo kannst du feststellen, in welcher Programmzeile der Thread gerade (wenn überhaupt) beschäftigt ist. Ich würde aber für die Identifizierung der Threads bei jedem Thread-Pbjekt Cargo auf eindeutigen Wert setzen.
Gruß,

Andreas
VIP der XUG Osnabrück
Benutzeravatar
AUGE_OHR
Marvin
Marvin
Beiträge: 12903
Registriert: Do, 16. Mär 2006 7:55
Wohnort: Hamburg
Hat sich bedankt: 19 Mal
Danksagung erhalten: 44 Mal

Beitrag von AUGE_OHR »

hi,
brandelh hat geschrieben: wenn du eine lokale Variabel Getlist := {} verwendest, kann jeder Thread seine eigene haben
ok klar.
brandelh hat geschrieben: , aber ich denke im alten CRT @ SAY etc. Modell sollte man es nicht mit verschiedenen Threads übertreiben.
yup, deshalb muss bei mir Thread B erst beendet werden bevor Thread A
wieder was tun darf.
brandelh hat geschrieben: Da ist viel über globale Variablen geregelt und das kann bei mehreren Threads nur Ärger geben.
bei der umstellung auf Cl*pper v5.x hab ich so ziemlich alle PRIVAT raus
geschmissen. Es sind eigenlich nur paar "ID_" PUBLIC variabeln die ich
auch für die ErrorSys benötige.
brandelh hat geschrieben: Erst in der Voll-GUI ist die Trennung einzelner Fenster sauber möglich.
klar da kann ich auch von einem "Fenster" ins andere "springen".
Alle neuen Applicationen werden nur noch als Class in GUI geschrieben
was die Sache deutlich leichter macht.

Ich wollte nur eine "dual-SDI" haben wobei der Grundgedanke darin
besteht das bei "alten" Cl*pper Programmen ja meistens "horizontal"
die "Stammdaten" sind und erst "vertikal" die eigenlichen "Bearbeitungs"
Menupunkte (Neu, Edit, Suchen, Drucken etc.) kommen.

Auch ist das Menu (bei mir) nicht "belastet" d.h. noch keine DBF offen
etc. . Dito beim "verlassen" eines "vertikalen" Menu Punktes wurde
vorher "aufgeräumt" sodas ich den Thread beruhigt "terminieren" kann.

Ich lasse das "vertikale" Menu als Thread laufen sodas ich "horizontal"
einfach in einen anderen Menupunkt "springen" kann um da schnell was
zu machen.

Das schöne dabei ist das ich "nur" das Menu austausche und der Rest
des Hybridcode (fast) unverändert lassen kann :)

gruss by OHR
Jimmy
Antworten