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

Status
Not open for further replies.
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:

SS-2017-04-19_11.05.40.png


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:

Gary Miyakawa

Active Member
Licensed User
Longtime User
Yes, SQL and http for example are not related to ui

This is my testing code and it definitely doesn't "sleep" for 30 seconds.

B4X:
'Non-UI application (console / server application)
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
#End Region

Sub Process_Globals
  
End Sub

Sub AppStart (Args() As String)

    Log(DateTime.Now)
    hold(30000)
    Log(DateTime.now)
    StartMessageLoop
End Sub

Sub hold(time As Int)
  
    Sleep (time)
  
End Sub

Thoughts ? BTW, I'm running this in a RPI...
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
It is async.
That means that between the 2 datetime.now will not be any delay.
That's 100% correct.

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

Only the resumable sub itself is paused and resumed.
Correct code:
B4X:
Sub AppStart (Args() As String)
    hold(30000)
    StartMessageLoop
End Sub

Sub hold(time As Int)
    Log(DateTime.Now)
    Sleep (time)
   Log(DateTime.now)
End Sub
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Tip: As explained in the first post, when one sub calls a second resumable sub, the code in the first sub will continue after the first Sleep or Wait For call (in the second sub).

If you want to wait for the second sub to complete then you can raise an event from the second sub and wait for it in the first:
B4X:
Sub FirstSub
   Log("FirstSub started")
   SecondSub
   Wait For SecondSub_Complete
   Log("FirstSub completed")
End Sub

Sub SecondSub
   Log("SecondSub started")
   Sleep(1000)
   Log("SecondSub completed")
   CallSubDelayed(Me, "SecondSub_Complete")
End Sub

Logs:
FirstSub started
SecondSub started
SecondSub completed
FirstSub completed

Notes:
- It is safer to use CallSubDelayed than CallSub. CallSub will fail if the second sub is never paused (for example if the sleep is only called based on some condition).
- There is an assumption here that FirstSub will not be called again until it is completed.
- In B4J v5.50 beta #2 there will be a warning about a missing sub. This is fixed for the next update.
 

Kirby Leeper

Member
Licensed User
Longtime User
Resumable subs is a new feature added in B4J v5.50. 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:

SS-2017-04-19_11.05.40.png


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

View attachment 54879

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

View attachment 54883

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

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.
- Waiting for a resumable sub to complete: https://www.b4x.com/android/forum/threads/resumable-subs-sleep-wait-for.78601/page-2#post-499130

This feature will be added to B4A and B4i in the next versions.
Just fansastic work. I'm glad I found B4X.
 

rwblinn

Well-Known Member
Licensed User
Longtime User
Thanks for this great feature.
Build a small test app with a Progressbar Class which is called by the Main Class via timer.
As this is a new feature, pls share if there are other / better ways to show an updating progressbar in a class.

EDIT: Removed Wait For and added Sleep (based on guidance Post #28).

Main Class Snippet
B4X:
Sub Process_Globals
    Public tmr As Timer
    Public tmrinterval As Long = 500
    Private ProgressValue As Int = 0
    Private ProgressDlg As ProgressDialog
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    ProgressDlg.Initialize(True)
    tmr.Initialize("tmr", tmrinterval)
    tmr.Enabled = False
End Sub

Sub tmr_Tick
    ProgressValue = ProgressValue + 10
    ProgressDlg.ProgressUpdate(ProgressValue)
    Sleep(0)
    If ProgressValue = 100 Then tmr.Enabled = False
End Sub

ProgressBar Class Snippet
B4X:
Sub Class_Globals
    Private fx As JFX
    Private frm As Form
    Private ProgressBar1 As ProgressBar
    Private btnClose As Button
    Private mCloseAtCompletion As Boolean = False
End Sub

Public Sub Initialize(CloseAtCompletion As Boolean)
    frm.Initialize("frm", 200, 100)
    frm.SetFormStyle("UNDECORATED")
    frm.Resizable = False
    frm.RootPane.LoadLayout("ProgressDialog")
    ProgressBar1.Progress = 0
    mCloseAtCompletion = CloseAtCompletion
    btnClose.Visible = Not(mCloseAtCompletion)
End Sub

'Update the progress bar with a value between 0 and 100
public Sub ProgressUpdate(value As Double)
    frm.show
    ProgressBar1.Progress = Round(value) / 100
    If mCloseAtCompletion And ProgressBar1.Progress = 1 Then frm.close
End Sub

Sub btnClose_Action
    frm.close
End Sub

upload_2017-4-25_17-13-1.png
 

Attachments

  • waitforprogressbar.zip
    3.7 KB · Views: 864
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
I don't think that you are using Wait For correctly. It is not needed in the code you posted.

Example of using Sleep to update the progress bar inside a long loop:
B4X:
Sub SubThatDoesAVeryLongCalculation
   Dim mx As Long = 100000000
   Dim i As Long
   For i = 1 To mx
     If i Mod 10000 = 0 Then
       ProgressDlg.ProgressUpdate(i * 100 / mx)
       Sleep(0)
     End If
   Next
End Sub

Note that I made a few changes to your project. See the attachment.
 

Attachments

  • SleepWithProgress.zip
    3.7 KB · Views: 922

rwblinn

Well-Known Member
Licensed User
Longtime User
Thanks for clarity = gives a better understanding when to use Wait For and Sleep.
For the test app I made, by keeping on using a timer, have removed the Wait For (in AppStart) and added Sleep(0) to the tmr_Tick after Progress Update, which works fine.
 

Swissmade

Well-Known Member
Licensed User
Longtime User
Can not wait to get the Release.
Thanks Erel
 

EnriqueGonzalez

Expert
Licensed User
Longtime User
Hello!

This work:

B4X:
            CallSubDelayed(Main, "initSQL_Complete")

    wait for initSQL_Complete 'in Main

But triggers an IDE warning for Sub initSQL_Complete not found!
 
Last edited:

EnriqueGonzalez

Expert
Licensed User
Longtime User
I have a question regarding duck typing.

i am working on a small example where i am lazy enough to write a code several times:

B4X:
    'Panels, tableviews, radioButtons.
    Dim n() As Node = Array As Node (Pane1,twPerfiles,twEmpleos,rdbSubTabla0,rdbSubTabla1)
   
    For Each n2 As Node In n
        n2.Alpha = 0
        n2.visible = True
        n2.SetAlphaAnimated(750,1)
       
        wait for (n2) pane1_AnimationCompleted
       
    Next

It will run for pane1 only, after that it will just wait eternally for twPerfiles, because the name of the event is not the same.
will it be possible to run this event with duck Typing?
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
after that it will just wait eternally for twPerfiles
Note that the program will not actively wait for the event. It is similar to having a sub that is never called.

You will need to set the EventName parameter of all the nodes to be the same.

Or use Sleep(750) instead of waiting for the event.
 

jahswant

Well-Known Member
Licensed User
Longtime User
This can never be supported as the code execution returns when Sleep or Wait For are called. The sub is later resumed. Long after the calling sub has already completed.
:rolleyes::rolleyes::rolleyes::rolleyes::rolleyes: I understand now.
 

leitor79

Active Member
Licensed User
Longtime User
Great Feature, thank you very much!

I have a question; I don't get why the output of the code quoted here is different than the output in this gif:

codeflow-gif.54923



Regards,

Tip: As explained in the first post, when one sub calls a second resumable sub, the code in the first sub will continue after the first Sleep or Wait For call (in the second sub).

If you want to wait for the second sub to complete then you can raise an event from the second sub and wait for it in the first:
B4X:
Sub FirstSub
   Log("FirstSub started")
   SecondSub
   Wait For SecondSub_Complete
   Log("FirstSub completed")
End Sub

Sub SecondSub
   Log("SecondSub started")
   Sleep(1000)
   Log("SecondSub completed")
   CallSubDelayed(Me, "SecondSub_Complete")
End Sub

Logs:
FirstSub started
SecondSub started
SecondSub completed
FirstSub completed

Notes:
- It is safer to use CallSubDelayed than CallSub. CallSub will fail if the second sub is never paused (for example if the sleep is only called based on some condition).
- There is an assumption here that FirstSub will not be called again until it is completed.
- In B4J v5.50 beta #2 there will be a warning about a missing sub. This is fixed for the next update.
 

EnriqueGonzalez

Expert
Licensed User
Longtime User
because of the wait for here

B4X:
 Wait For SecondSub_Complete

without this line, sub1 will continue even when sub2 has not finished.

this line is telling the code, wait until sub2 is finished to continue with sub1.
 
Status
Not open for further replies.
Top