Android Tutorial [java] Libraries and multithreading

Erel

B4X founder
Staff member
Licensed User
Longtime User
In order to keep Basic4android simple for developers, Basic4android code runs under the main UI thread.
Internally Basic4android has good support for multithreading.
Libraries developers are responsible for understanding and using the internal framework correctly.

Android shows the "application not responding" dialog after the main thread is blocked for more than 5 seconds. This means that lengthy actions such as network actions should be done with secondary threads.
This is indeed the case with Http library and AsyncStreams (and others as well).

Internal thread pool
An internal thread pool is available for handling background tasks. The pool is created on demand.
You can submit Runnables to this pool by calling:
B4X:
BA.submitRunnable
public [B]static[/B] Future<?> submitRunnable(Runnable runnable, Object container, int TaskId)
You should pass null and 0 to the second and third parameters.

Note that the pool is limited to 20 concurrent threads. A RejectedExecutionException will be raised if more tasks are submitted.

Raising events on the main thread
In most cases the background task will run some task and when it finishes, it will raise an event. As the user (B4A developer) expects his code to run on the main thread, the library developer should delegate the event to the main thread.
This is done by calling BA.raiseEventFromDifferentThread.
B4X:
public Object raiseEventFromDifferentThread(final Object sender,
         final Object container, final int TaskId, 
         final String event,
         final boolean throwErrorIfMissingSub, final Object[] params)
sender - The object assigner to the Sender keyword.
container and TaskId: pass null and 0.
event - lower cased event sub.
throwErrorIfMissingSub - Whether to throw an error if there is on such sub.
params - The event parameters.

BA.raiseEventFromDifferentThread is responsible for delegating the event to the main thread. This is actually more complicated than it may sound as the activity might be paused when the event is raised.
For example, the user can start a download and then go to the home screen.
In that case, this method will store the event in an event queue and the event will be processed right before Activity_Resume.

The code of BA.raiseEventFromDifferentThread:
B4X:
if (processBA != null)
         return processBA.raiseEventFromDifferentThread(sender, container, TaskId,
               event, throwErrorIfMissingSub, params);
      Runnable runnable = new B4ARunnable() {
         @Override
         public void run() {
            if (ignoreEventsFromOtherThreadsDuringMsgboxError) {
               Log.w("B4A", "Event: " + event + ", was ignored.");
               return;
            }
            if (!isService && activityBA == null) {
               Log.v("B4A", "Reposting event: " + event);
               handler.post(this); //don't raise event during the activity creation.
            }
            else if (isActivityPaused) {
               if (isService)  {
                  Log.w("B4A", "Ignoring event as service was destroyed.");
               }
               else {
                  Log.w("B4A", "sending message to waiting queue.");
                  if (messagesDuringPaused == null)
                     messagesDuringPaused = new ArrayList<Runnable>();
                  if (messagesDuringPaused.size() > 10) {
                     Log.w("B4A", "Ignoring event (too many queued events: " + event + ")");
                  }
                  else {
                     messagesDuringPaused.add(this);
                  }
               }
            }
            else {
               if (container != null)
                  markTaskAsFinish(container, TaskId);
               raiseEvent2(sender, false, event, throwErrorIfMissingSub, params);
            }
         }
      };
      handler.post(runnable);
      return null;
   }

Example taken from Network.Connect:
B4X:
   public void Connect(final BA ba, final String Host, final int Port, final int TimeOut) throws UnknownHostException {
      Runnable r = new Runnable() {

         @Override
         public void run() {
            Socket mySocket = socket;
            try {
               InetAddress Address = InetAddress.getByName(Host);
               InetSocketAddress i = new InetSocketAddress(Address, Port);
               if (mySocket != socket)
                  return;
               socket.connect(i, TimeOut);
               ba.raiseEventFromDifferentThread(SocketWrapper.this,
                     null, 0, eventName + "_connected", true, new Object[] {true});
            } catch (Exception e) {
               if (mySocket == socket) {
                  ba.setLastException(e);
                  ba.raiseEventFromDifferentThread(SocketWrapper.this,
                        null, 0, eventName + "_connected", true, new Object[] {false});
               }
            }
         }
         
      };
      ba.submitRunnable(r, this, 0);
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
There is a new helper method that you can use to simplify the task of running tasks in the background.

It is currently available in B4J 1.00 (beta 5). It will be added to B4A in the next update.

The method signature is:
B4X:
public static void runAsync(final BA ba, final Object Sender, String FullEventName,
       final Object[] errorResult, final Callable<Object[]> callable)

ba - The standard ba object.
Sender - This will set the sender. In most cases it should be 'this'
FullEventName - The event prefix together with the event itself. Note that the case is not important here.
errorResult - This will be passed to the event in case of an error.
Callable<Object[]> - Similar to Runnable. This is the code that will be executed in the background.

For example in the Excel library there is an Initialize method that opens the file:
B4X:
  public void Initialize(String Dir, String FileName) throws IOException, BiffException {
     InputStream in = File.OpenInput(Dir, FileName).getObject();
     Workbook w = Workbook.getWorkbook(in, getDefaultSettings(Encoding));
     in.close();
     setObject(w);
   }

Now this task can be slow so I wanted to provide an option to run it asynchronously:
B4X:
public void InitializeAsync(BA ba, String EventName, final String Dir, final String FileName) {
     BA.runAsync(ba, this, EventName + "_ready", new Object[] {false}, new Callable<Object[]>() {
       @Override
       public Object[] call() throws Exception {
         Initialize(Dir, FileName);
         return new Object[] {true};
       }
     });
   }

The event declaration is:
@Events(values={"Ready (Success As Boolean)"})
 

somed3v3loper

Well-Known Member
Licensed User
Longtime User
B4X:
     BA.runAsync(ba, this, EventName + "_ready", new Object[] {false}, new Callable<Object[]>() {

Can you please explain which argument for which ?
And I am trying to return anywheresoftware.b4a.objects.collections.List , How should I do it ?
 

Erel

B4X founder
Staff member
Licensed User
Longtime User

somed3v3loper

Well-Known Member
Licensed User
Longtime User

somed3v3loper

Well-Known Member
Licensed User
Longtime User
And this

B4X:
public anywheresoftware.b4a.objects.collections.List call() throws Exception {
 

DonManfred

Expert
Licensed User
Longtime User
???
 

somed3v3loper

Well-Known Member
Licensed User
Longtime User
Yeah I know :D I am just entering this world

No.

Can you post the non asynchronous code?

I fixed it thanks and sorry for my stupid questions :)

B4X:
public void do_task(final String dir ,final String zfile)    throws IOException {
       
        ba.runAsync(ba, this, eventName + "_done",  new Object[] {false}, new Callable<Object[]>() {
       @Override
       public Object[] call() throws Exception {
        // I was trying to get a list from the line below like list1= return_list(dir,zfile)
        return new Object[] { return_list(dir,zfile)};
       
       }
     });
   
    
    }
 
Top