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

Status
Not open for further replies.

Erel

Administrator
Staff member
Licensed User
New video tutorial:


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:



Sleep

Using Sleep is simple:
B4X:
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).
B4X:
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:
B4X:
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:
B4X:
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, 0, 0, Pane1.Width, Pane1.Height)
   MainForm.Show
   Wait For gmap_Ready '<----------------
   gmap.AddMarker(10, 10, "Marker")
End Sub
A bit more complicated example with FTP:
Listing all files in a remote folder and then downloading all the files:
B4X:
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, False, File.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:
B4X:
'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:
B4X:
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:
B4X:
'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

B4X:
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:

Erel

Administrator
Staff member
Licensed User
More examples:
Running an external program with jShell without blocking the UI:
B4X:
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
B4X:
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:
B4X:
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:
B4X:
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.
B4X:
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", 51042, 30000)
   Wait For socket_Connected (Successful As Boolean)
   If Successful Then
     astream.InitializePrefix(socket.InputStream, False, socket.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:
B4X:
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:
B4X:
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:

Ed Brown

Active Member
Licensed User
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.
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?
 

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.
B4X:
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.
 

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:

Erel

Administrator
Staff member
Licensed User
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?
There will be no memory leak. The previous resumable sub instance will be released.
 

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 ?
 

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?
 

Erel

Administrator
Staff member
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 ?
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

Expert
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
B4X:
WaitFor (job) JobDone(job AsHttpJob)
and not this
B4X:
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:

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).
 

Peter Simpson

Expert
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:

Erel

Administrator
Staff member
Licensed User
So after upgrading a couple of my projects with resumable subs, and I would just like to say WOW what an improvement, or as we say over here "this is the best thing since sliced bread
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:

Ed Brown

Active Member
Licensed User
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.
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.
 
Status
Not open for further replies.
Top