B4J Tutorial [B4X] Resumable Subs - Sleep / Wait For

Discussion in 'B4J Tutorials' started by Erel, Apr 19, 2017.

  1. Erel

    Erel Administrator Staff Member Licensed User

    Resumable subs is a new feature added in B4J v5.50 / B4i v4.00 / B4A v7.00. It dramatically simplifies the handling of asynchronous tasks.
    (This feature is a variant of stackless coroutines.)

    The special feature of resumable subs is that they can be paused, without pausing the executing thread, and later be resumed.
    The program doesn't wait for the resumable sub to be continued. Other events will be raised as usual.

    Any sub with one or more calls to Sleep or Wait For is a resumable subs. The IDE shows an indicator next to the sub declaration:

    [​IMG]

    Sleep

    Using Sleep is simple:
    Code:
    Log(1)
    Sleep(
    1000)
    Log(2)
    The sub will be paused for 1000 milliseconds and then be resumed.

    You can call Sleep(0) for the shortest pause. This can be used to allow the UI to be refreshed. It is a good alternative to DoEvents (which doesn't exist in B4J and B4i and should be avoided in B4A).
    Code:
    Sub VeryBusySub
       
    For i = 1 To 10000000
         
    'do something
         If i Mod 1000 = 0 Then Sleep(0'allow the UI to refresh every 1000 iterations.
       Next
       
    Log("finished!")
    End Sub
    As demonstrated in the following example, each call to a resumable sub creates a different instance which is not affected by other calls:
    Code:
    Sub btn_Action
       
    Dim b As Button = Sender
       
    For i = 10 To 0 Step - 1
         b.Text = i
         Sleep(
    100)
       
    Next
       b.Text = 
    "Takeoff!"
    End Sub
    takeoff.gif

    Wait For

    B4X programming language is event driven. Asynchronous tasks run in the background and raise an event when the task completes.
    With the new Wait For keyword you can handle the event inside the current sub.

    For example, this code will wait for the GoogleMap Ready event:
    Code:
    Sub AppStart (Form1 As Form, Args() As String)
       MainForm = Form1
       MainForm.RootPane.LoadLayout(
    "1"'Load the layout file.
       gmap.Initialize("gmap")
       Pane1.AddNode(gmap.AsPane, 
    00, Pane1.Width, Pane1.Height)
       MainForm.Show
       
    Wait For gmap_Ready '<----------------
       gmap.AddMarker(1010"Marker")
    End Sub
    A bit more complicated example with FTP:
    Listing all files in a remote folder and then downloading all the files:
    Code:
    Sub DownloadFolder (ServerFolder As String)
       
    FTP.List(ServerFolder)
       
    Wait For FTP_ListCompleted (ServerPath As String, Success As Boolean, Folders() As FTPEntry, Files() As FTPEntry'<----
       If Success Then
         
    For Each f As FTPEntry In Files
             
    FTP.DownloadFile(ServerPath & f.Name, FalseFile.DirApp, f.Name)
             
    Wait For FTP_DownloadCompleted (ServerPath2 As String, Success As Boolean) '<-----
             Log($"File ${ServerPath2} downloaded. Success = ${Success}"$)
         
    Next
       
    End If
       
    Log("Finish")
    End Sub
    When the Wait For keyword is called, the sub is paused and the internal events dispatcher takes care to resume it when the event is raised. If the event is never raised then the sub will never be resumed. The program will still be completely responsive.
    If Wait For is later called with the same event then the new sub instance will replace the previous one.

    Lets say that we want to create a sub that downloads an image and sets it to an ImageView:
    Code:
    'Bad example. Don't use.
    Sub DownloadImage(Link As String, iv As ImageView)
       
    Dim job As HttpJob
       job.Initialize(
    "", Me) 'note that the name parameter is no longer needed.
       job.Download(Link)
       
    Wait For JobDone(job As HttpJob)
       
    If job.Success Then
         iv.SetImage (job.GetBitmap) 
    'replace with iv.Bitmap = job.GetBitmap in B4A / B4i
       End If
       job.Release
    End Sub
    It will work properly if we call it once (more correctly, if we don't call it again before the previous call completes).
    If we call it like this:
    Code:
    DownloadImage("https://www.b4x.com/images3/android.png", ImageView1)
    DownloadImage(
    "https://www.b4x.com/images3/apple.png", ImageView2)
    Then only the second image will show because the second call to Wait For JobDone will overwrite the previous one.
    This brings us to the second variant of Wait For.
    To solve this issue Wait For can distinguish between events based on the event sender.
    This is done with an optional parameter:

    Wait For (<sender>) <event signature>

    Example:
    Code:
    'Good example. Use.
    Sub DownloadImage(Link As String, iv As ImageView)
       
    Dim job As HttpJob
       job.Initialize(
    "", Me) 'note that the name parameter is no longer needed.
       job.Download(Link)
       
    Wait For (job) JobDone(job As HttpJob)
       
    If job.Success Then
         iv.SetImage (job.GetBitmap) 
    'replace with iv.Bitmap = job.GetBitmap in B4A / B4i
       End If
       job.Release
    End Sub
    With the above code, each resumable sub instance will wait for a different event and will not be affected by other calls.

    Code Flow

    Code:
    Sub S1
    Log("S1: A")
    S2
    Log("S1: B")
    End Sub

    Sub S2
    Log("S2: A")
    Sleep(
    0)
    Log("S2: B")
    End Sub
    The output is:
    S1: A
    S2: A
    S1: B
    S2: B

    codeflow.gif

    Whenever Sleep or Wait For are called, the current sub is paused. This is equivalent to calling Return.

    Wait for a resumable sub to complete and return values from a resumable sub:

    https://www.b4x.com/android/forum/threads/b4x-resumable-subs-that-return-values-resumablesub.82670/

    Notes & Tips

    - Resumable subs cannot return a value.
    - The performance overhead of resumable subs in release mode should be insignificant in most cases. The overhead can be larger in debug mode. (If this becomes an issue then take the slow parts of the code and move them to other subs that are called from the resumable sub.)
    - Wait For events handlers precede the regular event handlers.
    - Resumable subs do not create additional threads. The code is executed by the main thread, or the handler thread in server solutions.
     
    Last edited: Aug 11, 2017
  2. Erel

    Erel Administrator Staff Member Licensed User

    More examples:
    Running an external program with jShell without blocking the UI:
    Code:
    Sub RunProcess
       
    Dim shl As Shell
       shl.Initialize(
    "shl""cmd"Array("/c""dir"))
       shl.Run(-
    1)
       
    Wait For (shl) shl_ProcessCompleted (Success As Boolean, ExitCode As Int, StdOut As String, StdErr As String)
       
    Log(StdOut)
    End Sub
    Code:
    Sub DownloadWithHttpJob
       
    Dim j As HttpJob
       j.Initialize(
    "", Me)
       j.Download(
    "http://www.google.com")
       
    Wait For(j) JobDone(j As HttpJob)
       
    If j.Success Then
         
    'work with result
         Log(j.GetString)
       
    End If
       j.Release
    End Sub
    Server handler - download a page and return it:
    Code:
    Sub Handle(req As ServletRequest, resp As ServletResponse)
       
    Log("handle")
       Download(resp)
       StartMessageLoop
    End Sub

    Sub Download (resp As ServletResponse)
       
    Dim j As HttpJob
       j.Initialize(
    "", Me)
       j.Download(
    "https://www.google.com")
       
    Wait For (j) JobDone(j As HttpJob)
       
    If j.Success Then
         resp.Write(j.GetString)
       
    End If
       j.Release
       StopMessageLoop
    End Sub
    Server WebSocket handler:
    Code:
    Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
       
    Log("Connected")
       ws = WebSocket1
       Download
    End Sub

    Sub Download
       
    Dim j As HttpJob
       j.Initialize(
    "", Me)
       j.Download(
    "https://www.google.com")
       
    Wait For (j) JobDone(j As HttpJob)
       
    If j.Success Then
         Result.SetHtml(j.GetString) 
    'Result is a JQueryElement (<p id="result"></p>)
         ws.Flush
       
    End If
       j.Release
    End Sub
    Non-ui application that connects to an ESP8266 server (https://www.b4x.com/android/forum/t...d-and-receive-objects-instead-of-bytes.72404/).
    It waits for the first message, prints it and closes the connection.
    Code:
    Sub Process_Globals
       
    Private astream As AsyncStreams
       
    Private ser As B4RSerializator
    End Sub

    Sub AppStart (Args() As String)
       ser.Initialize
       Connect
       StartMessageLoop
    End Sub

    Sub Connect
       
    Dim socket As Socket
       
    socket.Initialize("socket")
       
    socket.Connect("192.168.0.16"5104230000)
       
    Wait For socket_Connected (Successful As Boolean)
       
    If Successful Then
         astream.InitializePrefix(
    socket.InputStream, Falsesocket.OutputStream, "astream")
         
    Wait For astream_NewData (Buffer() As Byte)
         
    Dim data() As Object = ser.ConvertBytesToArray(Buffer)
         
    For Each d As Object In data
           
    Log(d)
         
    Next
         astream.Close
         StopMessageLoop
       
    End If
    End Sub
    B4i Specific

    Msgbox2:
    Code:
    Sub DeleteDocument
       
    Msgbox2("Msg""Do you want to do delete document?""Title"Array ("Yes""No"))
       
    Wait For Msg_Click (ButtonText As String)
       
    If ButtonText = "Yes" Then
         
    'delete document
         Msgbox("Document deleted!!!""")
       
    End If
    End Sub
    ActionSheet:
    Code:
    Sub ChooseAnimal
       
    Dim sheet As ActionSheet
       sheet.Initialize(
    "sheet"""""""Array("Cat""Dog""Monkey""Rat"))
       sheet.Show(Page1.RootPanel)
       
    Wait For sheet_Click (Animal As String)
       sheet.Initialize(
    "sheet"""""""Array("Male""Female"))
       sheet.Show(Page1.RootPanel)
       
    Wait For sheet_Click (Gender As String)
       
    Log($"You have chosen a ${Gender} ${Animal}"$)
    End Sub
     
    Last edited: May 7, 2017
  3. Ed Brown

    Ed Brown Active Member Licensed User

    Great work! Falling in love with B4X all over again.

    But to my question! Is the above quoted statement implying that calls to the same event will be overwritten while at the same time preventing a memory leak? ie. if the event is never raised and therefore not allowing the sub to be resumed, would this cause a memory leak of sorts?
     
  4. derez

    derez Expert Licensed User

    Using the resumeable sub already - its great :):
    I start it by setting show to true and run the sub, stop it by setting it to false. It runs together with the app while it gets large data from MySQL db.
    Code:
    Sub showwait
        
    Do While show
                
    ' display something
            Sleep(50)
        
    Loop
    End Sub
    A bug - copying the sub to the forum omitted the symbol of resumeable sub.
     
  5. Enrique Gonzalez R

    Enrique Gonzalez R Well-Known Member Licensed User

    I just used my first 3 wait fors!

    1 for Sql_ready
    and 2 for Job done!

    at the beginning i did not understand why my code was being executed ignoring the "wait for" for SQL. Then i noticed that, that was actually the idea!!!
     
    Last edited: Apr 19, 2017
  6. Erel

    Erel Administrator Staff Member Licensed User

    There will be no memory leak. The previous resumable sub instance will be released.
     
    Peter Simpson likes this.
  7. derez

    derez Expert Licensed User

    In several applications I call a server with various jobs and in one job done sub select the response by the job's name.
    If I split the response to each call using WaitFor, will the job done event return to the correct calling sub or I have to check the name there as well ?
     
  8. Cableguy

    Cableguy Expert Licensed User

    I have a question about the wait for event...
    In the example, wait for gmap_ready, if the sub Gmap-Ready exists, will the code inside it run before the sub resumes?
     
  9. stevel05

    stevel05 Expert Licensed User

    Erel said:
    So I guess the resumed sub will run first.
     
    Erel and derez like this.
  10. Jack Bodewes

    Jack Bodewes Member Licensed User

    I have a problem with B4J 5.50:

    For a = 0 To 100
    log(a)
    Next

    Error:
    a is not a valid identifier.
     
  11. Erel

    Erel Administrator Staff Member Licensed User

    This has nothing to do with this thread. Please start a new one in the questions or bugs forum.
     
  12. Erel

    Erel Administrator Staff Member Licensed User

    The name is no longer required. Dim and initialize HttpJob before each request and make sure to set the sender filter parameter (see the DownloadWithHttpJob example in the third post).
     
    Peter Simpson likes this.
  13. Peter Simpson

    Peter Simpson Well-Known Member Licensed User

    "A picture is worth a thousand words", well 2 gif's in this case.
    You really couldn't have demonstrated it any better.

    takeoff.gif codeflow.gif

    I just have to remember to use this
    Code:
    WaitFor (job) JobDone(job AsHttpJob)
    and not this
    Code:
    WaitFor JobDone(job AsHttpJob)
    But something is telling me that I will completely forget and ask the question in the future completely forgetting about this post (no 15).

    Thank you @Erel, resumable subs are an excellent addition to the B4X suite of development tools.
     
    Last edited: Apr 20, 2017
    jahswani and DonManfred like this.
  14. Erel

    Erel Administrator Staff Member Licensed User

    A new version of DBRequestManager (RDC client) is available: https://www.b4x.com/android/forum/t...-remote-database-connector.61801/#post-389984
    It allows using Wait For to handle the requests. Instead of writing three subs to handle a request, where two of the subs are shared by all requests, each request is handled in a single sub.
    The tags are no longer needed (they are kept for backwards compatibility).
     
    derez and b4auser1 like this.
  15. Peter Simpson

    Peter Simpson Well-Known Member Licensed User

    So after upgrading a couple of my projects with resumable subs, I would just like to say WOW what an improvement, or as we say over here "this is the best thing since sliced bread" :D

    Thank you @Erel, I can't wait for this to hit the other B4X solutions. B4X is really going places, up up and away...
     
    Last edited: Apr 21, 2017
    Kwame Twum, CHK, Jmu5667 and 2 others like this.
  16. Erel

    Erel Administrator Staff Member Licensed User

    Same feeling here.

    I've tried all kinds of methods that could help developers deal with asynchronous tasks. It took me 5 years to reach this solution which fits very naturally in B4X.
     
    Last edited: Apr 21, 2017
    Sandman, R2B2, David Meier and 13 others like this.
  17. Ed Brown

    Ed Brown Active Member Licensed User

    I think I can speak for the entire B4X community when I say that your efforts are very much appreciated. The updates keep rolling out and each update has brought either new language features, hardware support or both.

    I've been converting a number of my projects and I can definitely say that Resumable Subs 'feels' more natural and goes a long way to keeping the code simpler. I am looking forward to refactoring the B4a projects - some of which I know will greatly benefit this latest feature.
     
  18. derez

    derez Expert Licensed User

    In the new version the BytesToImage and ImageToBytes for B4j were deleted and only the versions for "not b4j" were left.
     
  19. Gary Miyakawa

    Gary Miyakawa Active Member Licensed User

    Should the "Sleep" function work on non-UI B4J applications ?
     
  20. Enrique Gonzalez R

    Enrique Gonzalez R Well-Known Member Licensed User

    Yes, SQL and http for example are not related to ui
     
Loading...