Josip Zohil, Koper, Slovenija, February 2015

Generators and coroutines to unblock a VFP application

 

Long running computations blocks the VFP application: it does not react to events (keyboard, mouse, timer...). The screen is frozen. Many times the application is not CPU bounded; it has only 1-10 % of CPU share. Guilty for this poor VFP performance are the programs; the way they are written. Generators, special type of functions, may unblock the computation and improve the responsiveness of a VFP application. In the article Closures and Generators in Visual FoxPro the generators are explained using functions (without objects). Programming with objects, add new capabilities to generators; they can move around their state.

VFP functions

When a VFP function is called, the body of the function executes straight away; from the start to the end or until the return statement or until an exception is thrown. The local variables inside the function are the state of the function. When a function terminates, local variables go out of scope (terminate); the state is not preserved. Such a function returns a value (or an exception).

Some VFP functions preserve state, for example, this one:

function  GOTOWA()

local s1

s1=7

select A  && "go to" a work area (channel) A

skip   && go to a next record

*suspend, pause execution in work area A

select B      && "go to" a work area (channel) B

*resume execution on work area B

brow   && a blocking command

*suspend (pause) execution on work area B

select A        && "GO TO" work area A, the record number (the state) does not change

*resume execution in work area A

s1=s1+2

*doevents

?recno(),s1

endfunc

The function GOTOWA is a "double iterator". It iterates over two work areas (A and B) and the records of the dbf table in the work area.

The first command of this function (SELECT A) tells VFP to pass control to the work area A. This work area is a "state" of this function. It was opened in another function and VFP preserve its state. We execute the block of commands "inside" the work area A, suspend (pause) execution, pass control to a work area B and resume execution of a block of code in this work area.

We may call this function multiple times, for example:

function TWOGOTO()

GOTOWA()

GOTOWA()

endfunc

The two function invocation maintains state (the two work areas and the record position inside them). We have two (different) results (the record numbers printed on the screen). The function "return" (print) two different values. The state is maintained automatically by VFP. On the other side, the local variable s1 doesn't maintain state, it goes out of scope, when the first function terminates and is redefined, when the second starts.

Note: If we defined  the variable s1  as private or public, it can maintain state.

We may look at the function TWOGOTO as an iteration over the set of functions: in the "first iteration" we call a function GOTOWA and on the second we do the same.

Much more interesting is the next composition:

function blockingLoop()

select A

do while not eof()

GOTOWA()  && skip,  go to a next record in the work area A

enddo

endfunction

We iterate over the records of the work area A till the end of a file is reached. Every call to the function GOTOWA produce a skip ( go to a next record) of the work area A. We can look at the function GOTOWA as an external call "move next" to the work area A. The state (the work area and the record number) is maintained automatically inside a function GOTOWA.

The function blockingLoop is a blocking computation: During its execution, it is on the VFP CALL STACK and doesn't permit VFP  to react to eventual events. It "takes away" the processor. The application is frozen.

Suspend able functions

 The function  blockingLoop is composed of multiple iterations. We can transform this iteration to a suspend able one. If we enable the DOEVENTS command in the GOTOWA function, this function becomes a suspend able function, a generator. It produces a sequence of results and is suspend able. When the function GOTOWA executes a command DOEVENTS it suspend execution, preserves the current state (of all local variables)  and pass control to the VFP event loop. If it finds "an event" in the event loop, it executes the event handler (a callback function). When it consumes the eventual event handlers in the event loop, it "resume" the function GOTOWA after the DOEVENTS command. In the interval suspend - resume VFP is reactive, it accepts eventual actions (keyboard, mouse click, timer events).

The command DOEVENTS is able to suspend a function execution and pass control to a VFP engine: The function GOTOWA without a DOEVENTS block the execution: doesn't permit other actions, it takes all the processors.

Sometimes, a function block waiting for an input, for data from a database…. For example, this command:

WAIT

blocks the processor and waits for a press on a keyboard button, without doing nothing. It blocks the processor! A very arrogant command.

Also, this

select * from test into cursor xx Where ID>100   && id is an indexed field

is a blocking command with very low processor share: it is waiting for the data (from the server) almost all the time. We may look at this query as an iterator (similar to a do while or scan composition).

We transform this iterator in a (non blocking) generator in this way:

function unblockQuery()

doevents

endfunc

select * from test into cursor xx Where ID>100 and unblockQuery()        && a generator

If we run this (long running) query, the VFP is responsive, it accepts user actions. The query executes in a normal way, "save state", pass control to a VFP event loop, executes the eventual event handler, return control to the query and resume from the "saved state".

COROUTINE

Generators are called also semi coroutine. Semi is because it passes control out of the function. The DOEVENTS suspend execution and pass control out (to the VFP event loop). There is no way to pass control back (from the event loop) to the suspended function.

Let us see an example when this is important.

function fLong()

local s1, s2, s3

s1=5

s2=f1(s1)

f2(s3)

s3=   && s3 is a result of a computation executed outside of the function fLong and we don't know, if it is terminated

f1(s3)

endfunction

This function has three state variables s1, s2 and s3. The value of s3 is the result of the computation running outside of the function fLong . For example, it is a computation spawned by a timer or is the result of a click on the keyboard. When the function fLong reaches a line s3= it must suspend and wait for a signal from the "outside". We are coordinating two processes: the function fLong  and an external process (for example a timer).

Note. The doevents command permits us to suspend execution. The timer process runs  in a concurrent (virtually parallel)  way with a function fLong. When VFP executes this function, we don't know, if the timer event has fired. We can proceed with a function  fLong only when we obtain the result from a timer. For example, the timer may fetch the data from the WEB. The two processes cooperate, so the name coroutine or cooperative multitasking (in our case a cooperation of two tasks).

Note. Usually, we implement this program by blocking execution (without concurrency). 

Note. In the  blockingLoop example, the "work area A" is waiting for an outside signal, to move to the next record.

The function fLong is a long running computation. We unblocks it using a command DOEVENTS. As said, it suspend and resume, but is not able to wait. How to make a function fLong waitable?

Note. In many languages this problem is solved by a YIELD command (p.e. JavaScript, C#) composed with an iterator.

There are various ways how to simulate a "yield" command in VFP: It must:

·       suspend execution,

·       preserve state,

·       accept an action from the "outside world",

·       accept a value from the "outside world",

·       continue execution.

Dovents has limited capabilities: It has only the ability to pause function execution and preserve state, it doesn't pass values out of the function. It pauses execution (suspend) and resume exactly from the point where it has paused. Doevents generate a pair (suspend, resume); it is a generator of this effect. When a Doevents resumes, the function is executed up to the point of the next Doevents (or to the end of the function or an exception is thrown).

A function with multiple Doevents acts:

·       like a generator (it produces suspend-resume actions),

·       like an iterator (we iterate over a collection of doevents comands),

·       it saves the entire state of the function (generator) : local variables, work areas, records positions...),

·       rather than returning a single time, a generator yield (return) multiple actions (suspend-resume).

Right before suspending the execution, Doevents save the state of the function. Upon entering the generator again, it restores the saved state and jump to where we left off.

A function with Doevents is more an iterable data structure than a function. We may add it an iterator interface, as we shall do later in this article. 

We may implement a generator similar to a Doevents with more powerful abilities. We iterate over the data structure (program, file - lines, collection), right before suspension (yielding) save the state of the generator. Upon entering the generator again, we restore the saved state and continue.

The state object

Let us start with a simple solution using an object to save the state and add it an iterator interface  (or derive the object from a class with an iterator interface).

Define Class stateObj As Custom

Protected v, currentv, donev, nloop, Count, Currentf

v=0   && value, state

nloop=0  && index

Count=10000000  && max number of iterations

Currentf=""  && current function, we iterate it

donev=.F.

Protected Function Init(fun, ncount)

This.Currentf=fun  && structure to iterate on

this.count=ncount

Endfunc

Function done()  && interface

Return This.donev

Endfunc

Function stateValue()  && interface

Return This.v

Endfun

Protected Function funcase(x)     && function to iterate on

Do Case

Case .nloop=1

*restore state and resume

.v=.v+1  && first function

*save state and suspend

Case .nloop=2

.v=.v+2  && second function

Otherwise

.v=.v+9   && third function

Endcase

Endfunc

Function fnext(x)  &&    x is an object, interface function

DoEvents    && suspend and pass control to the event loop

This.nloop=This.nloop+1   && increase the index

If This.nloop>This.Count Or This.donev    && is terminated?

This.donev=.T.

Return This

Endif

If Vartype(x)="N"  && or O-object, Does the caller send something?

This.v=x  && new received state

Endif

With This

xt=Evaluate("."+This.Currentf+"()")

Endwith

Return This     && return the object reference

Endfun

Function fun(x)       && another function to iterate, it is not protected

.v=.v+90

Endfunc

Enddef

We have a class stateObj with a member funcase ( a data structure we shall iterate over). This object has a function fnext - the interface; using it, we iterate over a function funcase. The object has protected state variables: v, nloop - iteration index, donev - iteration has reached the end of a function, currentf - a data structure to iterate over (a function) and count - maximal number of iteration allowed. We iterate the function one time (we can't move back). The state is stored in the object properties and is hidden (protected); is not accessible from the outside.

FUNCASE

It is composed of the three step blocks: when nloop is one (1) or two (2) or nloop is greater than 2. For  example, when nloop is equal 2, it starts executing at the command case .nloop=2. It takes the current state value v and increase it by two (2). After that it saves the state (inside the stateObj) and exit from the function. The function goes out of scope (it is not on the Call Stack).

We have a suspend able function. It executes, save state (suspend) and later restore state (resume), executes and save state (suspend) and so on. We are "moving" only forward.

This means that we can "pause" a function when it needs to wait for the result of another function (for example, we send  a request to a database, pause a current function and pass control to another function - a current function doesn't block).

FNEXT

The function fnext is a public method. When we call it, it increases the index (nloop) by one, checks if it reaches the end (nloop>count). In such a case the properties donev becomes .T.. In a lazy way it executes a function funcase and returns the stateObj. Each time we call fnext, it executes a function funcase with an appropriate index (nloop). A call to the function fnext can be explained in such a way: fnext takes a state of the function funcase, on this state it applies a function funcase. After that it saves the state and returns the stateObj.

The idea of a function fnext is:

·       it returns values from the function funcase (from the object),

·       we can send values to the object (see the next example),

·       the generator function cannot be called except by iterating over it.

OTHER functions

We have two public methods: done and stateValue. The first inform us, if the iteration is terminated and the second (stateValue) returns the current value of a property v.

Consumer

 

The function funcase acts as a producer; on each invocation, it produces a value. Let us now see, how to consume these values, how we manipulate the stateObj. The next function is an example of the implementation of the generator object.

Function caseConsumer()

LOCAL ncount

ncount=3    && iterate at most three (3) times

*a function funcase is a "data structure" we shall iterate over

om=Createobject("stateobj","funcase",ncount)  && state object for funcase

x1=om.fnext()

?x1.stateValue(),x1.done()

om.fnext()

?om.stateValue(),om.done()

*wait

om.fnext(5)  && v=5

?om.stateValue(),om.done()

om.fnext()  && 5+9=14

om.fnext()   && 14 , done=.T.

?om.stateValue(),om.done()

if om.done()

                             om.fnext()    && 14, done=.T.

                             ?om.stateValue(),om.done()

endif

Endfunc

In the last function, we create a generator stateObject and pass it the name of a function (funcase) we shall iterate and the maximal number of iterations (3). The reference (om) to this object is like a value machine that will generate values when we ask for them.

We execute this function using the command: caseConsumer().

The command x1=om.fnext()  calls the iterator function fnext: it iterates the first time, with nloop=1 and executes the block of code inside a function funcase after the command case .nloop=1. After that it returns a reference to the object (x1). A funcase continues to run until it voluntarily gives up control (when the block of code terminates). It suspends execution and goes out of scope (it is removed from a Call Stack).

A command

?x1.stateValue(),x1.done()

prints on the screen 1 and .F. (the value of v and donev). When VFP executes this command, the function caseConsumer is on the Call Stack; the function funcase is out of scope. Pay attention: on the VFP Call Stack are alternativelly jumping  two functions: funcase and caseConsumer. CaseConsumer is a caller. Funcase are waiting  the  call om.fnext(). When it receives the call, it executes funcase, mutate the state and return an object new state. After that it waits. We have the possibility to execute the function funcase on demand: we first request to execute the first do case clause, then the next and so on.

SEND

The command

om.fnext(5)

has the additional ability to send a value to the function funcase. It sends a value 5, which become the internal state of a properties v, after that a function fnext operates as  a fnext without an argument.

The function  CaseConsumer is an iterative algorithm over a function funcase which maintain its own state.

Coroutine

 

The two routines funcase and caseConsumer behave as two coordinated routines. They are executing (virtualy) in parallel: they exchange values. The way the function funcase executes depends from the actions outside it; in our case the function caseConsumer. Also the function caseConsumer depends of the values received from funcase.

The function caseConsumer has the capabilities to pause the function funcase for a longer time interval. The function funcase "is paused" in a special way: it doesn't block the CPU!!!

Inside a function fnext is a command DOEVENTS. The construction caseObj behaves as an enriched generator. The last is not able to send values as does our composition of two routines.

Behind the scene is an additional benefit of the coroutines. As we said, the two functions alternate their presence in the call stack. They are bouncing on the Call Stack. When a function is removed from the Call Stack, because of the DOEVENTS, the control is passed to a VFP event loop and VFP is able to accept the user's actions: the function doesn't block. This bouncing is called also trampoline (In Trampolines and asynchronous execution in Visual FoxPro is presented the coroutines management using only functions - functional style).

From a concurrency point of view, the two coroutines run in a concurrent way; because they pass control to the VFP event loop, they execute in a concurrent way with the functions waiting in the VFP event loop. All the functions may run in "parallel", in a concurrent way. The two functions are suspending able.

The relatively complicated composition is easy to follow and reason what is going on. The iteration algorithm is easy to follow. It is similar to an iteration over the records of a table using only skip 1 (without skip -1 and similar commands).

It is easy to transform a function to a suspend able (unblocking) version: We insert a do case construction. A function, we iterate over is similar to  a collection.

A Fibonacci sequence

A Fibonacci sequence is defined by a recursive function:

Function fibonacci(N)

If N < 2

                           Return 1

Else

                             Return fibonacci(N-2) + fibonacci(N-1)   && the sum of the two consecutive integers

endif

Endfunc

For a sequence of N=1,2,3,...6 we obtain a Fibonacci sequence 1,1,2,3,5,8,13. Generating a Fibonacci sequence of larger numbers of N  using this recursive function is a very slow computation, for example for N=200 you will get a stack overflow or something similar; there are too many functions on the Call Stack.

Let us modify a little bit in our last example. We shall add two properties to our object (N1 and N2). The first part of our stateObj class will be:

Define Class stateObj As Custom

Protected v , currentv,donev,nloop,Count,Currentf,N1,N2

v=0   && value, state

N1=1

N2=1 …

·       continue

 

The function funcase for a Fibonacci sequence is defined in this way:

Protected Function funcase(x)

Do Case

Case .nloop<2

* ?"11111"

*restore state and resume

.v=1

Otherwise

temp = .N2

.N2 = .N2 + .N1     && cache the value - save state

.N1 = temp           && cache the value - save state

.v=.N2

*       ?.nloop,.n1,.n2,.v

*save state and suspend

Endcase

Endfunc

We create a sequence of Fibonacci numbers for N=200 using this block of commands:

Os=Createobject("stateObj","funcase",200)

i=0

Do While i<=200

   i=i+1

                Os.fnext()  && call a funcase (Fibonacci numbers)

Enddo

?Os.stateValue()

This block of commands executes very fast: it "iterates" over a function funcase for 200 times and on each iteration it saves the state and "suspend" execution. On the next iteration it "resume" from the saved state. There are no stack overflow: recursion is replaced by a generator.

A more frequent example

Frequently the consumer function is implemented using a loop as in the next example using a Do While loop.

Function loopConsumer()

LOCAL ncount

ncount=3

om=Createobject("stateobj","funcase",ncount) && state object for funcase

Do While !om.done()  && iterate over the entire function (from the start to the end)

om.fnext()

Enddo

?om.stateValue(),om.done()

Endfunc

In the last function the stateObj is created. The iteration is over a function funcase and is controlled by a Do While loop. In each step the function fnext is executed until the end is reached (om.done() is .T.).

We execute this function using the command: loopConsumer(). It prints  12 .T. (0+1+2+9=12).

We have a non blocking composition: the execution of a function funcase doesn't block the VFP. Also the Do While loop doesn't block.

The function funcase is like a FIFO queue. It is shared by the object and the caller function (in our case loopConsumer). The caller must loop until "the queue"is empty (donev is true). We have shared a special data structure. In case of the iteration over a table's records  a collection of records is a shared data structure.

Example: loopFunConsumer

In this example we use a method fun of the object stateObj. It is a normal object's method. It is a "fast" executing method, so we shall not divide it in do case blocks.

Function loopFunConsumer()

LOCAL ncount

ncount=6

om=Createobject("stateobj","fun",ncount) && state object for fun

Do While !om.done()

x1=om.fnext()

Enddo

?x1.stateValue(),x1.done()

Endfunc

The CreateObject command calls once to start the consumption of data.

We iterate over the function fun six (6) times. We "call" the object method fun for six times.

We execute this function using the command: loopFunConsumer(). It prints  540 .T. (6*90=540).

It doesn't block the screen. For example, run this function with a ncount equal to 100000.

We’ve got two “threads” of behavior (two programs flow): one that’s generating values (fun) and one that’s consuming them loopFunConsumer. Behind the scene there are also other eventual concurrent threads in the VFP event loop. We run these two threads together and coordinate them. The VFP engine  coordinates these two concurrent processes with others waiting in the VFP event loop. That is concurrency in VFP.

All the magic in this example is a result of:

·       the way the VFP manage it's event loop,

·       the doevents command hidden inside the function fnext,

·       the state object "derived" from an iteratable (has a fnext interface).

In this construction a function fun is a waitable function: we are iterating over it and it is "waiting" for a call. It is waiting in a non blocking way!

We can implement the last function also using this similar construction:

K=0

Do While k<6

k=k+1

x1=om.fun()   && in case fun are not protected

doevents  && don't block, pass control to a VFP loop

Enddo

The last code block is based on the supposition that fun is a public (not protected) function. We have less control over this composition: for example, another caller doesn't know  the current iteration index (the internal object state). We have no interface to send a value to the object.

Adding concurrency using a timer

In the next example we have a state object, a timer, that manipulates the object's values and the function  timerConsumer that coordinates the timer and the object.

Function timerConsumer()

LOCAL ncount

ncount=1000

om=Createobject("stateobj","funCase",ncount)    && state object for funcase

ot=Createobject("timer")  && create a timer object

ot.interval=1 && 1 ms

** error BINDEVENT(ot,"timer",om,"fnext",1)  && allowed do nesting level

BINDEVENT(ot,"timer",om,"fun",1)  && increase by 90

DO while om.stateValue()<1000 && do the work until the timer event happens

DOEVENTS

ENDdo

?om.stateValue(), om.done(), "timer calls"  && 1080

om.fnext(om.stateValue()+100)   && send a value

Do While !om.done()

om.fnext()

Enddo

ot.interval=0  && stop a timer

?om.stateValue(),om.done(),"fnext and timer calls" && 10165

Endfunc

The object function fun is not protected. Using a bindevent we bind a timer event with this function: when a timer event happens, the fun is called. Because the timer events doesn't fires immediately, we run a DO while om.stateValue()<1000 command, to consume the first ten timer's events. Then we send a value to the object and proceed as in the previous examples. At the end, we stop a timer (ot.interval=0).

We run the function  timerConsumer.  It prints 1080 and .F. for "timer calls"  and 10165 and .T. for "fnext and timer calls". The object, the timer and the function timerConsumer run in a concurrent way (virtually parallel). In all the three constructions the program manipulates functions and/or values inside this constructions in a concurrent (and suspend able) way.

Conclusion

 VFP programmers frequently  suspend and resume  a block of code in their programs, when they navigate in the work areas or move between records in a table. At each move (suspension) VFP automatically save the state and on eventual resumption it gives back the state. Usually these are blocking suspensions.

Signals generated by a mouse, keyboard, timers … might be generated much faster than the processor  can execute the respective event handlers. They are concurrently waiting in a VFP event loop to be executed later in a FIFO order. This is an asynchronous execution (is not executed immediately).

The main rule is: a function must never block (execute for a long period of time); it must yield control to a VFP engine also during its long running process.

Also in VFP we can run, suspend able functions; that means, the VFP application is not frozen and is reactive (accepts the user actions). In simple words, we can easily break up a long running function (task) into some smaller step and pause the function using a doevents. It is easy:

·       to implement the iterator interface,

·       insert a doevents command,

·       write a function that save state inside an object,

·       in special (rare) cases, use a do case construction.