Josip Zohil, Koper, Slovenija, july 2013
It is difficult to write parallel and asynchronous code in Visual FoxPro (VFP). In this article I shall describe some problems that can be solved in parallel and/or in asynchronous way. In the article Trampoline and asynchronous execution in VFP I have presented a trampoline method to fetch WEB resources in an asynchronous way; without blocking. In this article I shall illustrate how to parallelize execution using a state machine method.
Let us start with a simple example. We have to write the names of two kids (LIAM and TARA) on the screen. They are concurrent, to avoid problems, we accept to write their names on the screen in parallel. We have used the same techniques as when we manage them on a trampoline, for example: three bounce LIAM and after that three TARA.
The names have to be visible on the screen, so we shall write LIAM's name horizontally and TARA's name vertically.
function forcey()
ACTIVATE screen
DO WHILE .t.
genprinty()
If MOD(i,tasklen+1)>=tasklen && test stop
exit
ENDIF
enddo
endfunc
Function genprinty() &&
closure on i
i=i+1
*kmes=kmes+1
@24-MOD(i,23),MOD(j,80)+1 say xname
Endfun
We shall use a function genprintx () to print LIAM's names vertically
(y coordinate). The screen has 24 rows and 80 columns. This is a generator
function (see, Closures and generators in VFP). Using a closure it takes the
values for i and xname from the environment.
We force genprintx() to be printed for tasklen times and then
stop.
A similar pair of functions we have for concurrent TARA:
Function forcex()
ACTIVATE screen
DO WHILE .t.
genprintx()
If MOD(j,6*(tasklen+1))>=6*tasklen && test stop
exit
ENDIF
enddo
endfunc
Function genprintx()
j=j+6
@1+MOD(i,23),1+MOD(j,80) say yname
Endfun
A function genprinty print TARA names horizontally. For a moment do not be confused with the MOD function. Suppose the screen coordinate behaves in a correct way.
Let us create a manager using a state machine with these states:
Start -> LIAM -> TARA-> ... STOP
After the initialization phase we start with a state LIAM, force genprinty to print, for example three times, after that we pass to state TARA and force genprintx to print three times. We pass from state to state for 20 times and then we stop (pass to state stop).
The coordinator is a function statemachine:
FUNCTION statemachin() && three states
xname="LIAM" && Print (bounce) LIAM
yname="TARA" && Print (bounce) TARA
stopstate=.f. && stop print (bounce )
i=-1
j=-1
k=0
state =
xname &&start state
*pred="stopstate"
tasklen=3 && print 3 times
tasklen=tasklen-1
*DIMENSION
mes(3)
DO WHILE .t.
newState = state
* kmes=0
DO case
CASE
state=xname
forcey()
&& print xname for tasklen times
newState = yname
IF NOT stopstate
*
SELECT mess
*
stopstate=mess.stop
ENDIF
CASE state=yname
forcex() && print yname for tasklen times
newState = xname
OTHERWISE
ENDCASE
k=k+tasklen+1
state=newstate
WAIT state wind TIMEOUT 0.5
IF k>60
&& 60/3=20
stopstate=.t.
endif
IF stopstate
EXIT
endif
enddo
ENDFUNC
In the VFP command window we run:
statemachin()
and the program starts writing on the screen: three times LIAM and
three times TARA. The command:
WAIT state wind TIMEOUT 0.5
Pauses the program execution for a half of a second. The program bounce LIAM and TARA for 20 times on a trampoline.
Let us compose the two functions forcex and forcey to execute in a serial order: first we run forcey for 20 times and than forcex 20 times.
We create a function:
Function statemachinser(N,funexy,predicate)
xname="LIAM"
yname="TARA"
stopstate=.F.
i=-1
j=-1
k=0
*pred="stopstate"
tasklen=N
tasklen=tasklen-1
*DIMENSION
mes(3)
Do While .T.
Eval(funexy)
If funexy="forcex()"
i=i+1
Else
j=j+6
Endif
k=k+tasklen+1
Wait
"working" Wind Timeout 0.5
If k>20*N
stopstate=.T.
Endif
If Not Evaluate(predicate)
**stopstate
* SELECT resource
* stopstate=stop
Endif
If Evaluate(predicate)
*stopstate
Exit
Endif
Enddo
Endfunc
And we execute it:
Clear
statemachinser(3,"forcey()","stopstate")
statemachinser(3,"forcex()","stopstate")
This program writes on the screen 20 times LIAM and after that 20 times TARA.
1) First user's observation, TARA is not satisfied with this serial execution; its name appears too late on the screen.
2) Second, expert observation: In case, there is an error in the function forcey, when i=13, we shall have written on the scree only LIAM names; Using a statemachine program, we shall have twelve LIAM and twelve TARA, a better »production«.
Generators are composable; it is better if we compose them in parallel.
We can write a state machine program as a trampoline (see Trampolines and asynchronous execution in VFP):
Function trampmachin(N,predicate,First,Second)
xname="LIAM" && A, three states, print
(bounce) LIAM
yname="TARA" && B Print (bounce) TARA
stopstate=.F. && C, stop print (bounce )
i=-1
j=-1
k=0
*pred="stopstate"
tasklen=N
tasklen=tasklen-1
*DIMENSION
mes(3)
Do While .T.
Evaluate(First)
*kmes=0
If Evaluate(predicate)
Exit
Endif
Evaluate(Second)
k=k+tasklen+1
* state=newstate
Wait "working" Wind Timeout 0.5
If k>N*20
stopstate=.T.
Endif
If Not stopstate
* SELECT resource
* stopstate=stop
Endif
If Evaluate(predicate)
Exit
Endif
Enddo
Endfunc
A program trampmachine is similar to statemachine; it is a little bit simpler. We run it in a VFP command window:
Clear
trampmachin(3,"stopstate","forcey()","forcex()")
This program has four arguments. Inside the program is only logic:
a composition of two functions and a predicate to stop execution. We can pass
to the program trampmachine other arguments, for example we run:
trampmachin(1,"stopstate","forcey()","forcex()")
In the last example concurrency for the kids was resolved with
»one bounce on a trampoline«.
You can organize also more kids on a trampoline: the programming
technique remains the same.
In VFP we mostly coordinate generators and a more natural way is
to manage them using a trampoline technique.
A function statemachinser is written to be executed serially. It
is similar to a trampmachine function (parallel execution), so it is easy to
write a function that executes in parallel, when we have a model that execute
serially.
In the next example we shall show the execution of the function ststemachin() is asynchronous. The process forcey() or LIAM is producing messages and the process forcex() is receiving these messages in an asynchronous (non blocking) way. forcey() is producing messages, when it produces a message it continue its work. The second process forcex receive these messages and continue its work.
In the next example a process LIAM will publish messages about the coordinate of his jumps (three jumps with three coordinate x and y). A process TARA will be a subscriber to these messages, after receiving them she will write the coordinates on the screen.
We modify the function statemachin(); we remove the star (*)
before the command DIMENSION mes(3), and kmes=0. Remove also the star before
kmes=kmes+i in the function genprinty and genprintx:
Function genprinty() &&
closure on i, producer
i=i+1
kmes=kmes+1
*generate a
message, (an array component or a field in a table)
mes(kmes)=STR(24-MOD(i,23),2)+","+STR(MOD(j,80)+1,2)
@24-MOD(i,23),MOD(j,80)+1 say xname
Endfun
Function genprintx() &&closure on j, consumer
j=j+6
kmes=kmes+1
*consume a
message in an asynchronous way
@1+MOD(i,23),1+MOD(j,80) say mes(kmes)
Endfun
When you run a modified statemachin it will display three times a LIAM name and instead of TARAs' names three pairs of the coordinates of LIAMs' jumps. The data are presented in parallel on the screen.
A modified program is a non blocking and asynchronous code: the LIAM process executes, pauses, TARA process writes the received data on the screen and so on.
A mes array is a message queue. A LIAM process puts in it three messages and a TARA process consumes them.
The difference between a synchronous and asynchronous execution is also the possibility to cancel (stop) the asynchronous process. Let us write an example of passing messages using a VFP table. We have a data base table mess with a field (column) stop L (logical), and one record. We shall use this table as a message channel to send messages to our state machine. In the program statemachin remove the stars (*) in the lines:
SELECT mess
stopstate=mess.stop
Before starting the statemachin we open the table:
Select 0
Use mess
clear
Statemachin()
On another computer we open a table mess and we replace a field stop with .T.. A statemachine program will stop before executing all the 24 loops.
A state machine is a technique to coordinate functions (programs) execution. We have an interest in compositions that allow us to inject code. These are possible when a coroutine (function, process) stops and later resume. We mostly coordinate generators. I have presented two techniques state machine and trampoline to coordinate VFP procedures. Generators are composable, so you can create entire application using these techniques. My opinion is, the best way is to combine them with the VFP event loop.