B4J Question How to wait on Recursive routine

Robert Valentino

Well-Known Member
Licensed User
Longtime User
I have the below recursive routine.

There is over 20,000 directories / files that this is doing, it hangs the UI while doing it.

How do I write this recursive routine as something I can wait on that will allow the UI to work smoothly


B4X:
Sub Process_Globals
   Private fx As JFX
   
   Private mFileTools               As FileTools   
end Sub

Public  Sub ListOfFiles(Directory As String, Files As List)

           Dim FileList           As List = mFileTools.ListFiles(Directory)
           Dim DirList               As List = mFileTools.ListDirs(Directory)
  
           Dim TempFile           As String
  
           If  FileList.IsInitialized And FileList.Size > 0 Then
               For i = 0 To FileList.Size-1
                   TempFile = FileList.Get(i)
                   TempFile = TempFile.ToLowerCase
          
                   If  TempFile.EndsWith(".mp3") Then          
                       Files.Add(Directory &"\" &FileList.Get(i))
                   End If
               Next
           End If
  
  
           If  DirList.IsInitialized And DirList.Size > 0 Then
               For i = 0 To DirList.Size-1
                   ListOfFiles(Directory &"\" &DirList.Get(i), Files)
               Next
           End If
                      
           Return
End Sub
 

Roycefer

Well-Known Member
Licensed User
Longtime User
Erel will yell at you if you use the Threading library. He will probably recommend that you use Sleep(0) in your loops to allow the message queue to process messages and update the UI.
 
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
see also CallSubDelayed and Wait For
 
Upvote 0

Robert Valentino

Well-Known Member
Licensed User
Longtime User
Tried CallSubDelayed and the Wait For on a recursive routine seems to return right away.

I thought the threading library was the way to go in B4J (will investigate)
 
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
you can use a top sub which call the recursive.
but here is a example without calling the same sub again and again. (i believe it was from Erel)
its a different technic. if you will use the wait for here you need to modify.
B4X:
Sub GetTheFolderContents (fldr As String) As List

    Dim flist As List
    flist.Initialize

    Dim folders As List
    folders.Initialize
    folders.Add(fldr)
    Do While folders.Size > 0
        fldr = folders.Get(0)
        folders.RemoveAt(0)
        MainForm.Title = $"Total files:${flist.Size}"$
        'Sleep(0)
        Dim entries As List = File.ListFiles(fldr)
        If entries.IsInitialized Then
            For Each x As String In entries
                If File.IsDirectory(fldr, x) Then
                    folders.Add(File.Combine(fldr, x)) ' Found a subfolder
                Else
                    If x.EndsWith(".b4a")=True Or x.EndsWith(".b4j")=True Or x.EndsWith(".b4i")=True  Then
                        flist.Add(File.Combine(fldr, x)) ' Found a file; add it to list 'fList'
                    End If
                End If
            Next
        End If
    Loop
 
    Return flist
 
End Sub

how to use for less files
B4X:
    Dim flist As List
    flist.Initialize
 
    If File.IsDirectory(Path,"")=True Then
        flist=GetTheFolderContents(Path)
    Else    
        fx.Msgbox(MainForm,"your path is not a directory","Message")
    End If
 
Upvote 0

keirS

Well-Known Member
Licensed User
Longtime User
Erel will yell at you if you use the Threading library. He will probably recommend that you use Sleep(0) in your loops to allow the message queue to process messages and update the UI.

I am not sure why. jSQL has ExecQueryAsync for long running SELECT statements. So why not use a thread for grabbing a load of file names?
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
I am not sure why. jSQL has ExecQueryAsync for long running SELECT statements. So why not use a thread for grabbing a load of file names?
There are many libraries that do many things with background threads. However the B4X code is expected to run on the main thread.
Tried CallSubDelayed and the Wait For on a recursive routine seems to return right away.
You need to implement it correctly. See the resumable subs video tutorial.

Example:
B4X:
Sub Process_Globals
   Private fx As JFX
   Private MainForm As Form
   Private Label1 As Label
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   MainForm = Form1
   MainForm.RootPane.LoadLayout("1") 'Load the layout file.
   MainForm.Show
   Dim res As List
   res.Initialize
   Wait For (CollectFilesAsync("C:\Users\Erel\Documents", res)) Complete (success As Boolean)
   Log($"Found :$1.0{res.Size} files"$)
End Sub

Sub CollectFilesAsync(RootFolder As String, List As List) As ResumableSub
   Label1.Text = RootFolder
   For Each f As String In File.ListFiles(RootFolder)
       If File.IsDirectory(RootFolder, f) Then
           Sleep(0)
           Wait For (CollectFilesAsync(File.Combine(RootFolder, f), List)) Complete (Success As Boolean)
       Else
           List.Add(File.Combine(RootFolder, f))
       End If
   Next
   Return True
End Sub
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Ninja'd: Should have refreshed browser.

See remarks in code for additional Wait/For modifications
B4X:
'Non-UI application (console / server application)
#Region Project Attributes
   #CommandLineArgs:
   #MergeLibraries: True
#End Region

Sub Process_Globals
   Private mFileTools As FileTools
End Sub

Sub AppStart (Args() As String)
   Test("src")
   'StartMessageLoop needed in non-ui environment to process Wait/For properly
   StartMessageLoop
End Sub

'Just a sub to take care of stopping message loop in non-ui environment
Public Sub Test(dir As String)
   Dim Files As List
   Files.Initialize
   Wait for (ListOfFiles(dir, Files)) Complete (Result As Int)
   Log(Files)
   Files.Clear
   Log(Files)
   Wait For (ListOfFiles2(dir)) Complete (FileList As List)
   Log(FileList)
   'Below mimics original sub's method of adding files to the parameter
   If FileList.Size > 0 Then Files.AddAll(FileList)
   Log(Files)
   StopMessageLoop
End Sub

'Closest to original file. Uses int return so it can be used with Wait/Complete
Public Sub ListOfFiles(Directory As String, Files As List) As ResumableSub
   Wait for (ListOfFiles2(Directory)) Complete(Result As List)
   Files.AddAll(Result)
   Return Files.Size
End Sub

'Modified to have the file list as return value
Public  Sub ListOfFiles2(Directory As String) As ResumableSub

   'May want to make ListFiles resumable
   Dim FileList As List = mFileTools.ListFiles(Directory)
   Sleep(0) ' The above may have taken awhile, let the system process some events
   'May want to make ListDirs resumable
   Dim DirList As List = mFileTools.ListDirs(Directory)
   Sleep(0) ' The above may have taken awhile, let the system process some events
  
   Dim TempFile As String
   Dim Files As List
    Files.Initialize
  
   If  FileList.IsInitialized And FileList.Size > 0 Then
       For i = 0 To FileList.Size-1
           'For every 100 files, let system process events. Adjust for responsiveness
           If i Mod 100 = 0 Then Sleep(0)
           TempFile = FileList.Get(i)
           TempFile = TempFile.ToLowerCase
        
           If  TempFile.EndsWith(".mp3") Then
               Files.Add(Directory &"\" &FileList.Get(i))
           End If
       Next
   End If
 
   If  DirList.IsInitialized And DirList.Size > 0 Then
       For i = 0 To DirList.Size-1
           Wait for (ListOfFiles2(Directory &"\" &DirList.Get(i))) Complete (TempFiles As List)
           Files.AddAll(TempFiles)
       Next
   End If
                    
   Return Files
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
   Return True
End Sub
 
Upvote 0
Top