B4J Tutorial [B4X] How Resumable Subs work

Discussion in 'B4J Tutorials' started by agraham, Jan 29, 2019.

  1. agraham

    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
    Code:
    Sub Button1_Click
       
    Wait For(Sum(12)) 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
    Code:
    Sub Button1_Click
       
    Dim rs As ResumableSub = Sum(12)
       
    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
    Code:
    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.
    Code:
    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(12)
                   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: Jan 30, 2019
    Jorge M A, miker2069, jroriz and 15 others like this.
  2. DonManfred

    DonManfred Expert Licensed User

    Suggestion:
    you should prefix the threadtitle with [B4X] in this case. The searchengine will recognize the thread as beeing multiplatform.
     
  3. GMan

    GMan Well-Known Member Licensed User

    Seems to be like the "old" Gosub ?
     
  4. agraham

    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.
     
    emexes, fredo, Gunther and 2 others like this.
  5. Erel

    Erel Administrator Staff Member Licensed User

    Excellent tutorial for the more curious developers :)

    Worth adding a simple example of the generated state machine.
     
  6. agraham

    agraham Expert Licensed User

    Done!
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice