B4J Tutorial [B4X] How Resumable Subs work

agraham

Expert
Licensed User
This document is intended to add a little understanding as to how Resumable Subs actually work under the bonnet and is intended as a complement to how they are used as Erel explains in his tutorials. Note that the following applies equally to B4A, B4I and B4J.

At the heart of the main thread of a B4X application there is a message loop from where all the Subs in the application are called. After a Sub finishes executing it returns to the message loop. The OS passes messages to this message loop which in turn calls the appropriate event Sub in the application. While Sub code is running in the application the message loop is stalled and can raise no further events, so a normal Sub cannot wait for an event to happen as the event cannot happen until the message loop is run after the Sub code has returned. Also Android will kill a process that doesn't run its message loop after a few seconds.

So there is a need to conveniently express the sequence, do something, wait for something, then do something. There are several techniques to achieve this, usually all involving a state machine (even if you don't think you are implementing one) that does part of a job, returns to the message loop and then when another event occurs runs more code to finish the job. Typically you might use the value of global variables to decide what needs to be done after the next event and call an appropriate Sub when an event occurs.

The Resumable Sub mechanism allows this "do, wait, then finish" to be conveniently expressed in a single Sub as sections of code separated by WaitFors. It reads as a single Sub but in fact, each section of code runs, returns to the message loop to wait and the Sub is then called again, when the WaitFor expires, to run the appropriate section of code. It is important that local variable values are preserved between invocations of the Sub - in computer science this is called a closure.

You can see the implementation of this in the generated Java code for a Resumable sub. The B4X compiler generates a class for each Resumable Sub. That class has instance fields that carry the local variable values for the Sub, a resume() method that contains the code for the Sub and a state variable that determines which section of the code in the resume() method is executed when resume() is next called. The code in resume() is rewritten by the compiler as a Select statement where the sections of code are placed in different Case statements.

The first call to a Resumable Sub creates the Resumable Sub instance, which sets its internal state to the starting value, and calls its resume() method. When the code execution reaches the Wait For statement it sets the internal state variable to indicate which Case statement to execute on the next call of resume() and calls a B4X internal method that saves a reference to the Resumable Sub class instance, sets up the message loop to call the Sub again when the right event happens and then returns to the Sub which returns to the message loop. The message loop can then carry on doing its job, pumping messages while the Resumable Sub class instance just sits there using no system resources other than a bit of memory.

When the expected event finally occurs code in the message loop calls the resume() method of the saved class instance which then executes the Case statement indicated by its state variable value. If there is another Wait For in the Sub then the same sequence occurs. When a Resumable Sub class instance resume() method finally returns with no further code to run it just returns to the message loop. There are now no saved references to that instance of the Resumable Sub so it is then garbage collected and destroyed.

More than one instance of a Resumable Sub class can be in existence at any one time as a new one is created on every call to a Resumable Sub. These instances are totally separate, have their own state and are independent of each other and live their own life cycles.

A Resumable Sub can wait for another Resumable Sub to complete before itself completing and can expect a return value from that other resumable Sub. Erel provides the example
B4X:
Sub Button1_Click
   Wait For(Sum(1, 2)) Complete (Result As Int)
   Log("result: " & Result)
   Log("after sum")
End Sub

Sub Sum(a As Int, b As Int) As ResumableSub
   Sleep(100)
   Log(a + b)
   Return a + b
End Sub
Where Button1_Click is equivalent to
B4X:
Sub Button1_Click
   Dim rs As ResumableSub = Sum(1, 2)
   Wait For(rs) Complete (Result As Int)
   Log("result: " & Result)
   Log("after sum")
End Sub
Let us examine what is actually happening here, when Sum is invoked and how the return value is managed.

Remember that both the Sum and Button1_Click Subs are rewritten by the compiler to create an instance of a Resumable Sub class that contains the code of the Sub in a resume() method. As a Resumable Sub, being an instance of a class type, is a reference type then we can Dim a variable of the type and assign a Resumble Sub instance to it. As the return type of Sum, in this example, is Resumable Sub then we can invoke Sum and assign its return value to a variable of that type.

At first sight this looks reasonable, we do this all the time – but in this case Sum will not have completed and have a result, that’s what we will wait for, so what is really happening?

Firstly let us look at how parameters passed to the Resumable Sub and those passed to the event are handled.

A Resumable Sub can have a set of parameters, like any Sub, but it can also wait for an event whose signature also has parameters. The parameter set of the event waited on will usually be different to those of the Resumable Sub and so are treated differently. When the compiler rewrites the code of the Resumable Sub it creates a constructor for the class that takes the parameters for the Resumable Sub itself and it makes resume() take a single parameter that is an Object array which will contains the parameters, if any, of the event being waited on.

When the event occurs any event parameter values associated with the event are packaged in an array of Objects, so individual parameters of any type can be passed. That array is passed to the resume() method whose compiler generated code unpacks the array into local variables of the name and type that were declared in the Wait For statement.

As previously described the first call to a Resumable Sub creates the Resumable Sub instance, passes any parameters to the constructor of the Resumable Sub class and calls its resume() method. When the code in resume() reaches a Sleep or Wait For it returns to the rewritten Resumable Sub which then returns that new instance of the Resumable Sub class which is saved to the ‘rs’ variable. This variable now holds a reference to the Resumable Sub class instance which has run to its first Wait For and returned. The B4X event dispatch mechanism (aka the message loop) knows from the Sleep or Wait For that this instance is waiting for a certain event to occur and will invoke its resume() method when it does.

As an aside notice the form of the Wait For statement in Button1_Click
B4X:
 Wait For (rs) Complete (Result As Int)
The (rs) part is actually optional. Without it the first Complete event that the message loop sees will be dispatched to the first Wait For statement that specified it. This optional part actually specifies the Sender of the event that will be awaited and ensures that the Resumable Sub instance will receive the event from the Sender instance that it expects to raise it. This is necessary when several instances of the same type might raise the same event in an indeterminate order, for example when downloading multiple files simultaneously.

Button1_Click in turn now calls Wait For on an event named ‘Complete’ which it expects to contain the actual return value from Sum which is an integer. After the invocation of Wait For it in turn immediately returns to the message loop which now has two Resumable Subs waiting for events and can now carry on pumping messages and running other code until one or other of these events are raised.

When the sleep period expires the sleep mechanism creates what is effectively a timer event that alerts the message loop to call resume() in the Sum Resumable Sub class instance. When Sum finally returns its value, being a Resumable Sub the act of returning raises a Complete event that the message loop sees, matches to the wait in Button1_Click and calls resume() in the Button1_Click Resumable Sub instance passing the integer result from Sum packaged in an Object array with one item which is now available in the 'Result' local variable.

For your further enlightenment here is a simplified psuedo-code outline of the compiler generated code for the example above.
B4X:
Sub Button1_click
   Dim rsub As ResumableSub_Button1_Click = New ResumableSub_Button1_Click
   rsub.resume(null)
End Sub

Class ResumableSub_Button1_Click
   Dim rs As ResumableSub_Sum
   Dim result As Int
   Dim state as Int
 
   ' Constructor called to create a new class instance
   Constructor ResumableSub_Button1_Click
       state = 0 ' entry to state machine'
   End Constructor
 
   ' This Sub implements the state machine
   Sub resume(results() as Object)
       Select state
           case 0:
               ' Create a new Resumable Sub instance and call its constructor
               Dim rs As ResumableSub_Sum = New Sum(1, 2)
               rs.resume(null)
               ' Set up for the wait, set the next state and then return
               Wait For (rs) Complete (result As Int)' doesn't wait here, just sets it up for after the Return
               state = 1 ' next state machine operation
               Return
           case 1:
               ' execution resumes here after the Complete event for ResumableSub_Sum rs
               result = results(0)
               Log("result: " & result)
               Return
       End Select
   End Sub
End Class


Class ResumableSub_Sum
   Dim a, b As Int
   Dim state as Int

   ' Constructor called to create a new class instance
   Constructor ResumableSub_Sum(pa As Int pb As Int)
       state = 0 ' entry to state machine'
       a = pa
       b = pb
   End Constructor

   ' This Sub implements the state machine
   Sub resume(results() As Object)
        Select state
           case 0:
               ' Set up for the sleep, set the next state and then return
               Sleep(100) ' doesn't sleep here, just sets it up for after the Return
               state = 1 ' next state machine operation
               Return
           case 1:
               ' execution resumes here after the sleep period ends
               Log(a + b)
               ' this call sets things up to raise a Complete event after we return
               ' Me is a reference to this instance to match to the correct waiting Sub
               ReturnFromResumableSub(Me, (a + b))
               Return
       End Select
   End Sub
End Class
 
Last edited:

GMan

Well-Known Member
Licensed User
Seems to be like the "old" Gosub ?
 

agraham

Expert
Licensed User
It's nothing like the old Gosub! CallSub is probably the nearest equivalent to GoSub as with that you can do run-time determination of which Sub to call.

This is a pause and resume capability that uses events raised on the message loop of the main thread to manage it.
 

CR95

Member
Licensed User
May I re-open this thread as I have some issues with "wait for" waiting for data event. It seems possible as you write :
A Resumable Sub can have a set of parameters, like any Sub, but it can also wait for an event whose signature also has parameters.
I found several samples explaining the writing of the "wait for" in Httpjob processes but I do not arrive to apply them to my "data ready" events
My B4J program wants to connect as a Client to a VideoProjector (Server) with TCP/IP.
After intialization phase (socket connection....), the program sends an instruction to the VP and then waits for its answer
B4X:
Wait For FluxVP_NewData (Buffer() As Byte) Complete (Result As String)

.....

Private Sub FluxVP_NewData (Buffer() As Byte) As ResumableSub
    ResultVP = BytesToString(Buffer, 0, Buffer.Length, "ASCII")
    MyLog("Réception de l'EPSON: " & ResultVP)
    Return ResultVP
End Sub
Compilation aborts with "Syntax error".
Please could you tell me what is wrong ?

Second question : VP randomly sends messages (called events to inform for example that source has changed..) to the client (my program)
How to "skip" these messages and continue to wait for the response to the previous demand ?
Of course, I can add a test to skip them BUT how to continue to "wait for" ?

Thanks in advance
 

agraham

Expert
Licensed User
In addition Sub FluxVP_NewData will not be executed as the Wait For will steal the event. Actually I'm a bit rusty on events and Resumable Subs and I'm not sure that your Wait For is the correct syntax to trap an event. Perhaps someone else can chime in here.
 
Last edited:

CR95

Member
Licensed User
Thanks Agraham,

But I am still confused
My understanding is that :
- in the "Wait for" instruction, "Buffer() as Byte" is linked to the subroutine in order to define the type of data coming from the VP after an event
- my subroutine returns a string (yes, it was not declared as a string) and my call is waiting for a string. That should match !

Following your remark, I changed in the subroutine the returned type to byte array and in the "Wait For" instruction, I wrote (result as Byte)
But the syntax error is still there
 

agraham

Expert
Licensed User
It is a bit confusing that Wait For can be used to both trap an event and to wait for an aynchronous Sub to complete. In this case you want to wait for the NewData event to occur so I think the syntax that you want is

Wait For FluxVP_NewData (Buffer() As Byte)

As the Wait For will steal the event you don't need the Private Sub FluxVP_NewData as it will never be called.
 

OliverA

Expert
Licensed User
Please could you tell me what is wrong ?
1) This really needs to be a new thread
2)
VP randomly sends messages (called events to inform for example that source has changed..) to the client (my program)
How to "skip" these messages and continue to wait for the response to the previous demand ?
Of course, I can add a test to skip them BUT how to continue to "wait for" ?
You don't use Wait For on FluxVP_NewData. You do something like this:

B4X:
expectingAnswer = True ' Declare expectingAnswer as Boolean in your Globals
astreams.write(demandBuffer) ' Send your demand to the VP
Wait For VPAnswer(Answer As String)

...
...

Private Sub FluxVP_NewData (Buffer() As Byte)
    If expectingAnswer = False Then Return ' ignore all incoming packets when we are not expecting anything
    Dim ResultVP As String = BytesToString(Buffer, 0, Buffer.Length, "ASCII")
    MyLog("Réception de l'EPSON: " & ResultVP)
    Dim haveDemandResponse As Boolean = False
    ' Place code here to check ResultVP and ignore any messages that do not pertain to a demand
    If haveDemandResponse Then ' we have a response to a demand
       expectingAnswer = False ' we've done waiting
       CallSubDelayed2(Me, "VPAnswer", ResultVP) ' This is the key. We only call VPAnswer when he have a response to a demand
    End If
End Sub
Note #1: un-tested
Note #2: You may need to use a timer to cover cases where you send a demand, but you never get an answer, otherwise, your Wait For may never execute/finish.
 

CR95

Member
Licensed User
Thanks
To Agraham : Yes. I confirm that the subroutine for processing "new data event" is never used if we limit to " Wait For FluxVP_NewData (Buffer() As Byte)". Control is directly passed to the line after the call.

To OliverA : I like your proposal based on CallSubDelayed to return to the calling program. Furthermore, your solution permits to manage all the possible error messages before returning. I will implement it tomorrow (now, it is night in France !) and let you know the result.
And yes, I thought to setup a timer to prevent possible timeout.
 

CR95

Member
Licensed User
To OliverA
Your solution works fine.
B4X:
Wait For VPAnswer(Answer As String)
.....
CallSubDelayed2(Me, "VPAnswer", ResultVP) ' This is the key. We only call VPAnswer when he have a response to a demand
This is a powerful instruction as it permits to wait and to jump from one process to another.
Unfortunately, I will never know why my instruction falls in syntax error
B4X:
Wait For FluxVP_NewData (Buffer() As Byte) Complete (Result As String)
Thanks
 

udg

Expert
Licensed User
Hi @Erel and @agraham ,
I know this is an old thread (strangely I found it only today), but luckily it's still open so I can post here a suggestion to make the great explanation above clearer to everybody. Eventually reserve the space for just a single post than close the thread, leaving it as a very useful resource for any B4Xer.

My suggestion is to attach a picture showing how the message loop is populated while the program flow goes from the first button click to the log of the final result.
Exactly like the push/pop actions on a stack on old programming books.
Eventually adding a numbered sequence of the steps so to use those same numbers in the textual description.

ps: please don't reply telling me I can do it myself..I am way too weak on graphics and related topics (yes, even on PowerPoint alikes..) :)
 

LucaMs

Expert
Licensed User
My suggestion is to attach a picture showing how the message loop is populated while the program flow goes from the first button click to the log of the final result.
Exactly like the push/pop actions on a stack on old programming books.
Eventually adding a numbered sequence of the steps so to use those same numbers in the textual description.
More briefly: a flow chart of the internal functioning of Resumables :)
 
Top