B4A Library Threading library

This library lets you run Basic4android Subs on separate threads to the main GUI thread. It also contains a Lock (semaphore) object that can be used for resource access arbitration and thread synchronisation.

Included is also an enhanced Exception object to give more information on any Exception caught. This can also rethrow those caught Exceptions and can create new Exceptions to throw.

EDIT :- Version 1.1 posted. See post #13 for details.
 

Attachments

  • Threading1.1.zip
    19.2 KB · Views: 7,226
Last edited:

LucianoB

Member
Licensed User
Longtime User
Hello, first of all thanks for this great library, great job.
I'm having a small issue using it. Hope somebody can help me to find a solution.
I have the following code, which works correctly.

B4X:
Sub CheckUpdates
    ...............
    upd1.Initialize
    upd1.ServerIPaddress = EditText3.Text
    upd1.ServerPort = EditText4.Text
    upd1.SQLDatabaseName = "dbremarksSQL"
    upd1.UserID = UserID
    upd1.Password = pwd
       
    upd1.QueryString = "SELECT sn from Air_sn"
    upd1.filenameToSave = "acserials.txt"
    upd1.CheckboxToCheck = cbxSn
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
   
End Sub

B4X:
Sub thrCommon_Sub1(obj1 As Object)
    Dim param As UpdateParams
    param= obj1
    Dim newdb As MSSQL
    newdb.setDatabase(param.ServerIPaddress & ":" & param.ServerPort,param.SQLDatabaseName,param.UserID,param.Password)
    Dim L1 As List = newdb.Query(param.QueryString)
    L1.RemoveAt(0)
    Dim str0,str1 As String
    For ii = 0 To L1.Size-1
        str0 = L1.Get(ii)
        L1.RemoveAt(ii)
        str1 = str0.Replace("]"," ").Trim.Replace("["," ").Trim
        L1.InsertAt(ii,str1)
    Next
    L1.Sort(True)
    File.Writelist(File.DirRootExternal & uptspath, param.filenameToSave, L1)
End Sub

Then, if I add more threads like in the following code, the new threads look like are not waiting for the previous one to die.

B4X:
Sub CheckUpdates
    ...............
    upd1.Initialize
    upd1.ServerIPaddress = EditText3.Text
    upd1.ServerPort = EditText4.Text
    upd1.SQLDatabaseName = "dbremarksSQL"
    upd1.UserID = UserID
    upd1.Password = pwd
   
   
   
    upd1.QueryString = "SELECT sn from Air_sn"
    upd1.filenameToSave = "acserials.txt"
    upd1.CheckboxToCheck = cbxSn
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
   

    upd1.QueryString = "SELECT f1 from Facility"
    upd1.filenameToSave = "facility.txt"
    upd1.CheckboxToCheck = cbxFacility
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
   
       
    upd1.QueryString = "SELECT DISTINCT zone_ac from ac_zone WHERE f1 = 'ZONE'"
    upd1.filenameToSave = "zone.txt"
    upd1.CheckboxToCheck = cbxZone1
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
End Sub

I want to use only one routine for many threads (I will have 13 different SELECT). So how to wait for a thread to die before start a new one?
Thank you very much for any help.
 

Informatix

Expert
Licensed User
Longtime User
Hello, first of all thanks for this great library, great job.
I'm having a small issue using it. Hope somebody can help me to find a solution.
I have the following code, which works correctly.

B4X:
Sub CheckUpdates
    ...............
    upd1.Initialize
    upd1.ServerIPaddress = EditText3.Text
    upd1.ServerPort = EditText4.Text
    upd1.SQLDatabaseName = "dbremarksSQL"
    upd1.UserID = UserID
    upd1.Password = pwd
      
    upd1.QueryString = "SELECT sn from Air_sn"
    upd1.filenameToSave = "acserials.txt"
    upd1.CheckboxToCheck = cbxSn
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
  
End Sub

B4X:
Sub thrCommon_Sub1(obj1 As Object)
    Dim param As UpdateParams
    param= obj1
    Dim newdb As MSSQL
    newdb.setDatabase(param.ServerIPaddress & ":" & param.ServerPort,param.SQLDatabaseName,param.UserID,param.Password)
    Dim L1 As List = newdb.Query(param.QueryString)
    L1.RemoveAt(0)
    Dim str0,str1 As String
    For ii = 0 To L1.Size-1
        str0 = L1.Get(ii)
        L1.RemoveAt(ii)
        str1 = str0.Replace("]"," ").Trim.Replace("["," ").Trim
        L1.InsertAt(ii,str1)
    Next
    L1.Sort(True)
    File.Writelist(File.DirRootExternal & uptspath, param.filenameToSave, L1)
End Sub

Then, if I add more threads like in the following code, the new threads look like are not waiting for the previous one to die.

B4X:
Sub CheckUpdates
    ...............
    upd1.Initialize
    upd1.ServerIPaddress = EditText3.Text
    upd1.ServerPort = EditText4.Text
    upd1.SQLDatabaseName = "dbremarksSQL"
    upd1.UserID = UserID
    upd1.Password = pwd
  
  
  
    upd1.QueryString = "SELECT sn from Air_sn"
    upd1.filenameToSave = "acserials.txt"
    upd1.CheckboxToCheck = cbxSn
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
  

    upd1.QueryString = "SELECT f1 from Facility"
    upd1.filenameToSave = "facility.txt"
    upd1.CheckboxToCheck = cbxFacility
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
  
      
    upd1.QueryString = "SELECT DISTINCT zone_ac from ac_zone WHERE f1 = 'ZONE'"
    upd1.filenameToSave = "zone.txt"
    upd1.CheckboxToCheck = cbxZone1
    thrCommon.Start(Me, "thrCommon_Sub1",  Array As Object(upd1))
End Sub

I want to use only one routine for many threads (I will have 13 different SELECT). So how to wait for a thread to die before start a new one?
Thank you very much for any help.
It's absolutely normal that a thread does not wait. It's its purpose! If you want what you expect, use a classic sub function.
 

LucianoB

Member
Licensed User
Longtime User
It's absolutely normal that a thread does not wait. It's its purpose! If you want what you expect, use a classic sub function.

Thank you for your quick reply. I'm still learing on how threads work.
I don't want to use classic sub functions because they will freeze the program until all subs are ended. The database is not in the device but in the local network.
So do you think I should create a different thread with a different name for each SELECT query? if yes I'll have to write 13 different subs plus 13 more for _Ended.
 

Informatix

Expert
Licensed User
Longtime User
Thank you for your quick reply. I'm still learing on how threads work.
I don't want to use classic sub functions because they will freeze the program until all subs are ended. The database is not in the device but in the local network.
So do you think I should create a different thread with a different name for each SELECT query? if yes I'll have to write 13 different subs plus 13 more for _Ended.
You could do that:
B4X:
Sub CheckUpdates
 ...............
 thrCommon.Start(Me, "thrCommon_Sub1", Null)
End Sub
Sub thrCommon_Sub1(obj As Object)
  Generic_Task( for query 1 )
  Generic_Task( for query 2 )
  Generic_Task( for query 3 )
  etc.
End Sub
Sub Generic_Task(obj as Object)
 'your SQL code and your WriteList
End Sub
 

LucianoB

Member
Licensed User
Longtime User
You could do that:
B4X:
Sub CheckUpdates
...............
thrCommon.Start(Me, "thrCommon_Sub1", Null)
End Sub
Sub thrCommon_Sub1(obj As Object)
  Generic_Task( for query 1 )
  Generic_Task( for query 2 )
  Generic_Task( for query 3 )
  etc.
End Sub
Sub Generic_Task(obj as Object)
'your SQL code and your WriteList
End Sub

Thank you very much for your suggestion!! Really interesting. I'll give it a try immediatelly.

Just a question about it. Is, in this case, the Generik_Task sub handled in background by thrCommon_Sub1 in a different thread (not in the main thread)?
I know that maybe it's a silly question but I'm still learning and want to understand correctly the thread handlings. :)
 

LucianoB

Member
Licensed User
Longtime User
I tried your suggestion and wanna say that it works perfectly!!!
Thank you very much for your help ;)
 

kkkpe

Active Member
Licensed User
Longtime User
I compiled the basic example of Threading library B4A 6:30 the Log returns the following error:


B4X:
LogCat connected to: 3204501379753175
matc--------- beginning of main
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **
main_btnstart_click (java line: 409)
java.lang.RuntimeException: Thread.Start : Sub threadsub1 not found!
    at anywheresoftware.b4a.agraham.threading.Threading.Start(Threading.java:207)
    at anywheresoftware.b4a.agraham.threaddemo.main._btnstart_click(main.java:409)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:157)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:153)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:78)
    at android.view.View.performClick(View.java:5242)
    at android.widget.TextView.performClick(TextView.java:10573)
    at android.view.View$PerformClick.run(View.java:21196)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:145)
    at android.app.ActivityThread.main(ActivityThread.java:6938)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
java.lang.RuntimeException: Thread.Start : Sub threadsub1 not found!
** Activity (main) Pause, UserClosed = true **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **
main_btnstart_click (java line: 418)
java.lang.RuntimeException: Thread.Start : Sub threadsub1 not found!
 
D

Deleted member 103

Guest
In log is "Sub threadsub1 not found!", You're certain that this "sub" exist?
 

kkkpe

Active Member
Licensed User
Longtime User
Yes, there.
Yes, there

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
   
    Dim Thread1 As Thread
    Dim Thread2 As Thread
    Dim Lock1 As Lock
    Dim Lock2 As Lock
    Dim end1 As Int
    Dim ok As Boolean
    Dim msg As String

End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
   
    Dim btnStart As Button
    Dim btnStatus As Button
    Dim btnStop As Button
    Dim btnError As Button
   
    Dim EditText1 As EditText
    Dim EditText2 As EditText   

    Dim ProgressBar1 As ProgressBar
End Sub


Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout")
    If FirstTime Then
        Thread1.Initialise("Thread1")
        Thread2.Initialise("Thread2")
    End If
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ThreadSub1
    end1 = 0
    Dim Count As Int
    Dim Params(1) As Object
    Do While Count < 1000
        Count = Count + 1
        Params(0) = Count
        end1 = Count
        ok = False
        Do Until ok
            ' this is because Android seems to lose the run message if the user presses back button
            ' this way no message will be ignored
            Thread1.RunOnGuiThread("Update1", Params)
            ok = Lock1.WaitFor(1000)
        Loop
    Loop
End Sub

Sub Update1(count As Int)
    EditText1.Text = count
    Lock1.Unlock
End Sub

Sub Thread1_Ended(fail As Boolean, error As String) 'An error or Exception has occurred in the Thread
    Msgbox(error, "Thread1 Ended")
End Sub

Sub ThreadSub2
    Dim Count As Int
    Dim Params(1) As Object
    Do While Count < 1000
        ' instead of locking like Thread1 we could crudely just wait for the GUI to update
        ' some messages may be lost but this may not matter in some applications
        Count = Count + 1
        Params(0) = Count
        Thread2.RunOnGuiThread("Update2", Params)
        Thread2.Sleep(10)
    Loop
End Sub

Sub Update2(count As Int)
    EditText2.Text = count
    ProgressBar1.Progress = count /10
    Lock2.Unlock
End Sub

Sub Thread2_Ended(fail As Boolean, error As String) 'An error or Exception has occurred in the Thread
    Msgbox(error, "Thread2 Ended")
End Sub

Sub btnStart_Click
    Dim args(0) As Object
    EditText1.Text = 0
    EditText2.Text = 0
    Lock1.Initialize(True)
    Lock2.Initialize(True)   
    Thread1.Name = "B4A Thread 1"
    Thread2.Name = "B4A Thread 2"
    Thread1.Start(Null, "ThreadSub1", args)       
    Thread2.Start(Null, "ThreadSub2", args)       
End Sub

Sub btnStatus_Click
    msg = "Lock1 : " & Lock1.LockState & " " & Thread1.Error & CRLF
    msg = msg & "Lock2 : " & Lock2.LockState & " " & Thread2.Error & CRLF
    Msgbox(msg, "Threading v" & NumberFormat2(Lock1.Version, 1, 1, 1, False))
End Sub

Sub btnStop_Click
    Thread1.Interrupt
    Thread2.Interrupt   
End Sub

Sub btnError_Click
    Dim Ex As ExceptionEx
    Try
        File.OpenInput("Non-existent", "file")
    Catch
        ' Catch saves the Exception to LastException which is a global variable.
        ' Because of this there is (at the moment) a 'hole' in Basic4Android Exception handling.
        ' If Exceptions are raised at the same time (admittedly a bit unlikely) on two different threads
        ' one may not see its own Exception in LastException but may see the one raised on the other thread.
        ' To mitigate this copy LastException as soon as possible to a local Exception object.
        ' The Dim for that local object is done at the start of the method to get the copy as close to Catch as possible
        Ex = LastException
        msg = Ex.StackTraceElement(0)
        Msgbox(msg, Ex.Name)
        Ex.Initialize("Oops! - No file found.")
        Ex.Throw
    End Try

End Sub
 

Informatix

Expert
Licensed User
Longtime User
Yes, there.
Yes, there

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
  
    Dim Thread1 As Thread
    Dim Thread2 As Thread
    Dim Lock1 As Lock
    Dim Lock2 As Lock
    Dim end1 As Int
    Dim ok As Boolean
    Dim msg As String

End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
  
    Dim btnStart As Button
    Dim btnStatus As Button
    Dim btnStop As Button
    Dim btnError As Button
  
    Dim EditText1 As EditText
    Dim EditText2 As EditText  

    Dim ProgressBar1 As ProgressBar
End Sub


Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout")
    If FirstTime Then
        Thread1.Initialise("Thread1")
        Thread2.Initialise("Thread2")
    End If
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ThreadSub1
    end1 = 0
    Dim Count As Int
    Dim Params(1) As Object
    Do While Count < 1000
        Count = Count + 1
        Params(0) = Count
        end1 = Count
        ok = False
        Do Until ok
            ' this is because Android seems to lose the run message if the user presses back button
            ' this way no message will be ignored
            Thread1.RunOnGuiThread("Update1", Params)
            ok = Lock1.WaitFor(1000)
        Loop
    Loop
End Sub

Sub Update1(count As Int)
    EditText1.Text = count
    Lock1.Unlock
End Sub

Sub Thread1_Ended(fail As Boolean, error As String) 'An error or Exception has occurred in the Thread
    Msgbox(error, "Thread1 Ended")
End Sub

Sub ThreadSub2
    Dim Count As Int
    Dim Params(1) As Object
    Do While Count < 1000
        ' instead of locking like Thread1 we could crudely just wait for the GUI to update
        ' some messages may be lost but this may not matter in some applications
        Count = Count + 1
        Params(0) = Count
        Thread2.RunOnGuiThread("Update2", Params)
        Thread2.Sleep(10)
    Loop
End Sub

Sub Update2(count As Int)
    EditText2.Text = count
    ProgressBar1.Progress = count /10
    Lock2.Unlock
End Sub

Sub Thread2_Ended(fail As Boolean, error As String) 'An error or Exception has occurred in the Thread
    Msgbox(error, "Thread2 Ended")
End Sub

Sub btnStart_Click
    Dim args(0) As Object
    EditText1.Text = 0
    EditText2.Text = 0
    Lock1.Initialize(True)
    Lock2.Initialize(True)  
    Thread1.Name = "B4A Thread 1"
    Thread2.Name = "B4A Thread 2"
    Thread1.Start(Null, "ThreadSub1", args)      
    Thread2.Start(Null, "ThreadSub2", args)      
End Sub

Sub btnStatus_Click
    msg = "Lock1 : " & Lock1.LockState & " " & Thread1.Error & CRLF
    msg = msg & "Lock2 : " & Lock2.LockState & " " & Thread2.Error & CRLF
    Msgbox(msg, "Threading v" & NumberFormat2(Lock1.Version, 1, 1, 1, False))
End Sub

Sub btnStop_Click
    Thread1.Interrupt
    Thread2.Interrupt  
End Sub

Sub btnError_Click
    Dim Ex As ExceptionEx
    Try
        File.OpenInput("Non-existent", "file")
    Catch
        ' Catch saves the Exception to LastException which is a global variable.
        ' Because of this there is (at the moment) a 'hole' in Basic4Android Exception handling.
        ' If Exceptions are raised at the same time (admittedly a bit unlikely) on two different threads
        ' one may not see its own Exception in LastException but may see the one raised on the other thread.
        ' To mitigate this copy LastException as soon as possible to a local Exception object.
        ' The Dim for that local object is done at the start of the method to get the copy as close to Catch as possible
        Ex = LastException
        msg = Ex.StackTraceElement(0)
        Msgbox(msg, Ex.Name)
        Ex.Initialize("Oops! - No file found.")
        Ex.Throw
    End Try

End Sub
If you use the Release+obfuscation mode, you should add an underscore to all sub names without one.
 

ToolboxZX

Member
Licensed User
Longtime User
Using the thread library, is it possible to start a sub in a code module, rather than the main one from the main?
(for example, if you had two modules: your "Main" and a code module "TEST" where the code module TEST has a sub "GoFish", how would use start "GoFish")

If so, what would be the syntax for that?
 

DonManfred

Expert
Licensed User
Longtime User
Code module does not have a context. Due to this it can not handle events.
So the answer is no. Use Classes or Services or Activitys
 

ardillaprogramadora

Member
Licensed User
I have a problem:

Dim args(0) As Object

Lock1.Initialize(True)
Thread1.Name = "B4A Thread 1"
Thread1.Start(Null, "prueba", args)

When i execute this code i get the following error:
java.lang.IllegalArgumentException: method anywheresoftware.b4a.agraham.threading.Threading.Start argument 3 has type java.lang.Object[], got java.lang.Object

im using v7.01 and testing in android 8.

Any idea?
 

stevel05

Expert
Licensed User
Longtime User
What happes if you change: Dim args(0) As Object to Dim args() As Object, or just pass Null
 

ardillaprogramadora

Member
Licensed User
What happes if you change: Dim args(0) As Object to Dim args() As Object, or just pass Null

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Dim Thread1 As Thread
    Dim Lock1 As Lock
    
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

End Sub

Sub Activity_Create(FirstTime As Boolean)
    
    
    'Do not forget to load the layout file created with the visual designer. For example:
    Activity.LoadLayout("Principal.bal")

    

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub
Sub test
    
    Msgbox("tst","test")
    
    
        
End Sub

Sub Button1_Click
    Dim args() As Object
    Thread1.Initialise("Thread1")
    Lock1.Initialize(True)
    Thread1.Name = "B4A Thread 1"
    Thread1.Start(Null, "test", Null)
    
    
End Sub

im getting an strange error: java.lang.RuntimeException: Thread.Start : Sub not found!
if i change "test" for test in Thread1.Start(Null, "test", Null), sub test gets executed, but then the app crashes...
 

stevel05

Expert
Licensed User
Longtime User
It is not possible to access gui objects directly from a thread, try changing the msgbox call to a Log statement
 
Top