Java Question Manage long operations without blocking main thread

max123

Well-Known Member
Licensed User
Longtime User
Hi all developers,

I wrote a native code wrapper for a library that manages Zip files
https://github.com/srikanth-lingala/zip4j
https://jar-download.com/artifacts/net.lingala.zip4j/zip4j/2.6.1/documentation

This library permit to do some interesting things with Zip files.

The wrapper I wrote seem to work well, but the only problem I had is that it blocks the main thread while do operations on Zip files.
This is not a big problem with small files, but is a real problem with large files where the main thread is blocked, I cannot raise
events on main thread to log the operation progress, change a value of a progressbar etc.

The library has _Progress event, this is fired on the Main thread, the user can print it on the log, set a progressbar value etc...
this currently happen but only when operation on files is done.
Events:
@Events(values={"Progress (Method As String, Percent As Int, Complete As Boolean)", "Error (Method As String, Error As String)"})

On library every command can thrown an exception and return a boolean value to inform the user if operation was successfully completed or error occoured.
So the user can do eg, something like this:
B4X:
Dim Success = Zip.ExtractZip(..., ..., ...)
If Success Then
   ... open here an extracted file
End If

I searched on the forum and found this good tutorial by Erel:
https://www.b4x.com/android/forum/threads/java-libraries-and-multithreading.12697/#content

I managed it to work in background using a Runnable as Erel suggested, the problem here is that Runnable cannot return a value, it's signature is 'void' and it cannot thrown an exception I catch with try-catch.

Searching on the web I found that a better solution is to use Callable, so I've read #post2 in the same Erel thread:
Java:
public static void runAsync(final BA ba, final Object Sender, String FullEventName,
       final Object[] errorResult, final Callable<Object[]> callable)

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);
 }
 
 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};
       }
     });
 }

I do not yet tried it, but the problem is that as Erel said on same thread, it can only return the value when the task completes:
Just to make sure that it is clear, the event is raised when the task completes.

You can use ba.raiseEventFromDifferentThread if you want to raise intermediate events.
... so the problem here seem to be I cannot anyway raise the _Progress event in the middle.

What I want is to return a boolean value from functions and even the _Error event where I pass the message string of exception occoured.
Naturally even raise _Progress event....

Because I'm not very Java expert I accept any suggestion to solve this, here I post just a function that use this mechanism, but my library
has 12 of these that I need to change to work the right way.

Any suggestion appreciated
Many Thanks
Max

Original blocking function:
public boolean ExtractZip(final String ZipPath, final String DestFolder, final String Password) {    
     
            // Initiate ZipFile object with the path/name of the zip file.
            ZipFile zipFile = new ZipFile(ZipPath);
     
            final String evt = eventName + "_progress";
            final String errEvt = eventName + "_error";
            final int Percent, oldPercent;
 
            try {

                Percent = 0; oldPercent = 0;
         
                // Set true to monitor progress state
                // This work on different thread
                zipFile.setRunInThread(true);
         
                // Check to see if the zip file is password protected
                if (Password.length() > 0)    {
                    try {
                        // If yes, then set the password for the zip file
                        zipFile.setPassword(Password.toCharArray());
                    } catch (Exception e) {
                        BA.Log("Zip4j: ExtractZip -> Cannot set Password for file " + ZipPath);
                        if (ba.subExists(errEvt)) {
                            ba.raiseEvent(this, errEvt, "ExtractZip", "Cannot set Password for file " + ZipPath);
                        }
                        return false;
                    }
                }
         
                // Extracts all files to the path specified
                zipFile.extractAll(DestFolder);
         
                // Get progress
                if (ba.subExists(evt)) {
                    ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
             
                    while (progressMonitor.getState().equals(ProgressMonitor.State.BUSY)) {     // New syntax
                      //BA.Log("Extracting. Percent done: " + progressMonitor.getPercentDone());
                        Percent = progressMonitor.getPercentDone();
                        if (oldPercent != Percent) {
                            //BA.Log("Zip4j: RAISE EVENT: " + evt);
                            ba.raiseEvent(this, evt, "ExtractZip", Percent, false);
                        }        
                        oldPercent = Percent;
                    }
                                         
                   // HERE OPERATION IS DONE
                    BA.Log("Zip4j: ExtractZip -> Done extracted content of file " + ZipPath +
                            " into Folder " + DestFolder + "  Result: " + getResultString(progressMonitor.getResult()));
                    ba.raiseEvent(this, evt, "ExtractZip", 100, true);
                } else {
                    BA.Log("Zip4j: ExtractZip -> Sub " + OriginalEventName +
                            "_Progress does not exists. Use Async Mode to extract file " + ZipPath +
                            " into Folder " + DestFolder);        
                }
         
                return true;
            } catch (ZipException e) {
                String err = e.getMessage().replace("net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: ", "");
                err = err.replaceAll(":", "");
                //BA.Log("Zip4j: ExtractZip -> " + err);
                if (ba.subExists(errEvt)) {
                    ba.raiseEvent(this, errEvt, "ExtractZip", err);
                }
                return false;
            } finally {
                try {
                    zipFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    if (ba.subExists(errEvt)) {
                        ba.raiseEvent(this, errEvt, "ExtractZip", e.getMessage());
                    }
                    return false;
                };
           }
 
        }

Unblocking. But cannot return value:
public void ExtractZip(final String ZipPath, final String DestFolder, final String Password) {    
     
            Runnable r = new Runnable() {

                @Override
                public void run() {

                    // Initiate ZipFile object with the path/name of the zip file.
                    ZipFile zipFile = new ZipFile(ZipPath);
             
                    final String evt = eventName + "_progress";
                    final String errEvt = eventName + "_error";
                    final int Percent, oldPercent;
             
                    try {
 
                        Percent = 0; oldPercent = 0;
                 
                        // Set true to monitor progress state
                        // Work on background thread
                        zipFile.setRunInThread(true);
                 
                        // Set if the zip file is password protected
                        if (Password.length() > 0)    {
                            try {
                                // if yes, then set the password for the zip file
                                zipFile.setPassword(Password.toCharArray());
                            } catch (Exception e) {
                                BA.Log("Zip4j: ExtractZip -> Cannot set Password for file " + ZipPath);
                                if (ba.subExists(errEvt)) {
                                    ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false,
                                        new Object[] {"ExtractZip", "Cannot set Password for file " + ZipPath});
                                }
                                return;
                            }
                        }
                 
                        // Extracts all files to the path specified
                        zipFile.extractAll(DestFolder);
                 
                        // Get progress
                        if (ba.subExists(evt)) {
                            ProgressMonitor progressMonitor = zipFile.getProgressMonitor();

                            while (progressMonitor.getState().equals(ProgressMonitor.State.BUSY)) {
                              //BA.Log("Extracting. Percent done: " + progressMonitor.getPercentDone());
                                Percent = progressMonitor.getPercentDone();
                         
                                if (oldPercent != Percent) {
                                    //BA.Log("Zip4j: RAISE EVENT: " + evt);
                                    ba.raiseEventFromDifferentThread(this, null, 0, evt, false, new Object[] {"ExtractZip", Percent, false});
                                }        
                                oldPercent = Percent;
                            }
                                 
                           // HERE OPERATION IS DONE
                            BA.Log("Zip4j: ExtractZip -> Done extracted content of file " + ZipPath +
                                    " into Folder " + DestFolder + "  Result: " + getResultString(progressMonitor.getResult()));
                            BA.Log("Zip4j: RAISE FINAL EVENT: " + evt);
                            ba.raiseEventFromDifferentThread(this, null, 0, evt, false, new Object[] {"ExtractZip", 100, true});
                        } else {
                            BA.Log("Zip4j: ExtractZip -> Sub " + OriginalEventName +
                                    "_Progress does not exists. Use Async Mode to extract file " + ZipPath +
                                    " into Folder " + DestFolder);        
                        }
                 
                        return;
                    } catch (ZipException e) {
                        String err = e.getMessage().replace("net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: ", "");
                        err = err.replaceAll(":", "");
                        //BA.Log("Zip4j: ExtractZip -> " + err);
                        if (ba.subExists(errEvt)) {
                            ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", err});
                        }
                        return;
                    } finally {
                        try {
                            zipFile.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                            if (ba.subExists(errEvt)) {
                                ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", e.getMessage()});
                            }
                            return;
                        };
                   }
     
                }
            };
            BA.submitRunnable(r, this, 0);
        }
 
Last edited:

agraham

Expert
Licensed User
Longtime User
Perhaps I am missing the problem but your non-blocking version looks fine. To return a value you could either add an additional parameter to your progress event that contains the final value that is only valid when progress is 100% or you could raise a different final event with whatever parameter values you need.

I don't understand why you need to return a literal string "ExtractZip" as a parameter in the events
 

max123

Well-Known Member
Licensed User
Longtime User
Many thanks @agraham for your reply.

As I wrote the event already have a parameter Complete to know when unzip operation is really done, this is in conjunction with Percent parameter.
@Events(values={"Progress (Method As String, Percent As Int, Complete As Boolean)", "Error (Method As String, Error As String)"})
I've added this to be sure the operation was really finished, if the Percent value is 100 this do not necessary mean that file unzip operation is done.

What I want is not only pass it on event function, but even return a boolean value from a function, if you see signature of my blocking it return a boolean, the non-blocking return void.
I need to do it to manage this way without use global flags on the Main event and check if the flag is True or False. just do this way:
B4X:
Dim Success = Zip.ExtractZip(..., ..., ...)
If Success Then
   ... open here an extracted file
End If
possibly without use Wait For

How I can handle both ?
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
I don't understand why you need to return a literal string "ExtractZip" as a parameter in the events
I added this is because the library has 30 and more functions, this way in the events handlers on the Main the user can know what command still execute.

Now I tried the runAsync but I've the same problem, it only raises the events, but I cannot know how to return True or False (based on try-catch exceptions) from the function. As you see I've 2 events, evt that is the progress and errEvt fired when error occour, in the non blocking function it fire the event even return boolean from a function.

It even seem to be slower than Runnable, I always have lower transfer average while unzip the same 40MB zip file.

Here the code:
Java:
public boolean ExtractZip(final String ZipPath, final String DestFolder, final String Password) {        
         
//            Runnable r = new Runnable() {
//
//                @Override
//                public void run() {
 
                    // Initiate ZipFile object with the path/name of the zip file.
                    final ZipFile zipFile = new ZipFile(ZipPath);
                 
                    final String evt = eventName + "_progress";
                    final String errEvt = eventName + "_error";
                 
                    BA.runAsync(ba, this, evt,  new Object[] {"ExtractZip", Percent, false}, new Callable<Object[]>() {
                       @Override
                       public Object[] call() throws Exception {
//                            return new Object[] {};
//                        
//                       }
//                    });
       
                    try {
     
                        //Percent = 0; oldPercent = 0;
                        int Percent = 0, oldPercent = 0;
                     
                        // Set true to monitor progress state
                        zipFile.setRunInThread(true);
                     
                        // Check to see if the zip file is password protected   // AGGIUNTA IO (ANCHE Password NEGLI ARGOMENTI PASSATI ALLA FUNZIONE)
                        //if (zipFile.isEncrypted()) {
                        if (Password.length() > 0)    {
                            try {
                                // if yes, then set the password for the zip file
                                zipFile.setPassword(Password.toCharArray());
                            } catch (Exception e) {
                                BA.Log("Zip4j: ExtractZip -> Cannot set Password for file " + ZipPath);
                                if (ba.subExists(errEvt)) {
                                    //ba.raiseEvent(this, errEvt, "ExtractZip", "Cannot set Password for file " + ZipPath);
                                    ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false,
                                 new Object[] {"ExtractZip", "Cannot set Password for file " + ZipPath + "  " + e.getMessage()});                    
                                }
                                //result = false;
                                //return new Object[] {false};
                            }
                        }
                     
                        // Extracts all files to the path specified
                        zipFile.extractAll(DestFolder);
                     
                        // Get progress
                        if (ba.subExists(evt)) {
                            ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
                            //while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
                            while (progressMonitor.getState().equals(ProgressMonitor.State.BUSY)) {     // New syntax
                              //BA.Log("Extracting. Percent done: " + progressMonitor.getPercentDone());
                                Percent = progressMonitor.getPercentDone();
                                //BA.Log("Zip4j: OLD PERCENT: " + oldPercent + "  PERCENT: " + Percent);
                                if (oldPercent < Percent) {
        //                            BA.Log("Zip4j: RAISE EVENT: " + evt);
        //                            ba.raiseEventFromDifferentThread(this, null, 0, evt, false, "ExtractZip", Percent);
//                                    ba.raiseEvent(this, evt, "ExtractZip", Percent, false);
                                   ba.raiseEventFromDifferentThread(this, null, 0, evt, false, new Object[] {"ExtractZip", Percent, false});
                                    //ba.raiseEvent2(this, true, evt, false, "ExtractZip", Percent);
                                }            
                                oldPercent = Percent;
//                                try {
//                                    Thread.sleep(5);
//                                } catch (InterruptedException e) {
//                                    e.printStackTrace();
//                                }
                            }
                     
        //                    /////////////
        //                    while (!progressMonitor.getState().equals(ProgressMonitor.State.READY)) {
        //                          System.out.println("Percentage done: " + progressMonitor.getPercentDone());
        //                          System.out.println("Current file: " + progressMonitor.getFileName());
        //                          System.out.println("Current task: " + progressMonitor.getCurrentTask());
            //
        //                          Thread.sleep(100);
        //                        }
            //
        //                        if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)) {
        //                          System.out.println("Successfully added folder to zip");
        //                        } else if (progressMonitor.getResult().equals(ProgressMonitor.Result.ERROR)) {
        //                          System.out.println("Error occurred. Error message: " + progressMonitor.getException().getMessage());
        //                        } else if (progressMonitor.getResult().equals(ProgressMonitor.Result.CANCELLED)) {
        //                          System.out.println("Task cancelled");
        //                        }
        //                    /////////////
                                         
                           // HERE OPERATION IS DONE
                            BA.Log("Zip4j: ExtractZip -> Done extracted content of file " + ZipPath +
                                    " into Folder " + DestFolder + "  Result: " + getResultString(progressMonitor.getResult()));
                            //ba.raiseEvent(this, evt, "ExtractZip", 100, true);
                            BA.Log("Zip4j: RAISE FINAL EVENT: " + evt);
                            ba.raiseEventFromDifferentThread(this, null, 0, evt, false, new Object[] {"ExtractZip", 100, true});
                            //return new Object[] {"ExtractZip", 100, true};
                        } else {
                            BA.Log("Zip4j: ExtractZip -> Sub " + OriginalEventName +
                                    "_Progress does not exists. Use Async Mode to extract file " + ZipPath +
                                    " into Folder " + DestFolder);
                            //return new Object[] {"ExtractZip", 0, false};
                        }
                     
                        //return new Object[] {true};
                        //return new Object[] {"ExtractZip", 100, true};
                    } catch (ZipException e) {
                        String err = e.getMessage().replace("net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: ", "");
                        err = err.replaceAll(":", "");
                        //BA.Log("Zip4j: ExtractZip -> " + err);
                        if (ba.subExists(errEvt)) {
                            ba.raiseEvent(this, errEvt, "ExtractZip", err);
                            ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", err});
                        }
                        //return new Object[] {false};
                        //return new Object[] {"ExtractZip", 0, false};
                    } finally {
                        try {
                            zipFile.close();                    
                        } catch (IOException e) {
                            e.printStackTrace();
                            if (ba.subExists(errEvt)) {
//                                ba.raiseEvent(this, errEvt, "ExtractZip", e.getMessage());
                                ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", e.getMessage()});
                            }
                            //return new Object[] {false};
                            //return new Object[] {"ExtractZip", 0, false};
                        };
                     
                   }
                           
                    return new Object[] {};
               
                       }
                    });

            return true;
        }
 
Last edited:

Spavlyuk

Active Member
Licensed User
I don't think it's possible for Zip.ExtractZip to return a value that it does not know at the time its called. Alternatively, you can raise a "Complete" event and pass a "Success" argument based on whether an exception was raised.
 

max123

Well-Known Member
Licensed User
Longtime User
@agraham I know you. But how I can do that ?
B4X:
Dim Success As Boolean = Zip.ExtractZip(..., ..., ...)
If Success Then
   ' ... open here an extracted file
End If
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
@Spavlyuk , if it thrown an exception it should return False, or True otherwise, but the problem here is that now we are on another thread
 

max123

Well-Known Member
Licensed User
Longtime User
Ok Many Thanks I will end up to use events.

And using Wait For to check the _Progress Complete param is not possible ?

Thanks
 
Last edited:

agraham

Expert
Licensed User
Longtime User
And using Wait For to check the _Progress Complete param is not possible
You should be able to use Wait For to catch any event but it won't do what you said you wanted which was a return value from a Sub launched on another thread. It is just another way of dealing with an event.
 

max123

Well-Known Member
Licensed User
Longtime User
@agraham, many thanks for clarifications. šŸ‘

I've changed my library with a Runnable on every function, but now I've obtained the opposed of what I wanted.

The library now do no blocks the Main thread, just skip Zip operations..... horrible. :eek:

What happen now is that probably, there are more concurrent threads that works at same time, on the log I see mixed logs from more that one operation, some operations are skipped and files not created or not readed.

This is not what I wanted.... no.... no...

I do not need that the library process more operations (and files) at same time, I need just one operation at time, maybe I do not explained well my situation.

Do no matter if Main thread blocks, this is what I want, the user need to wait operations finished, I just need that the GUI (that is on main thread) do no blocks, so the ProbressBar can show the progress value, and the log can show progress and infos during current operation, but not at the end of operations that have no sense at all.

This work with a blocking library version (without Runnables) if I put a DoEvents inside a event calllback function, but I want avoid this because as Erel said DoEvents can create problems. If I comment the DoEvents, all logs works, but the log show it after operation is done, the ProgressBar only refresh at 100% when operation is done.

Please, can someone help me to know how obtain this ?

Many thanks
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Please any help ?
I cannot release the library if I not solve this.

Note that I've wrote the same library for B4J too, but now to solve the problem I worked on B4A.
If I solve one I solve both.

Both the libraries are around 5200 lines of code (with comments) so this required a lot of time.
 
Last edited:

Spavlyuk

Active Member
Licensed User
Sounds like you're doing something wrong. I have code similar to the following working fine.
In B4A I have a couple of subs handling those events, although Wait For could also be used.
I'd suggest trying to work without progress until you get the basic stuff working, then see how you can add it later.

Java:
public void DoStuff() {
    final Executor executor = Executors.newSingleThreadExecutor();

    executor.execute(new Runnable() {
        @Override
        public void run() {
            try {
                // DoStuff...
                ba.raiseEventFromDifferentThread(this, null, 0, eventName + "_success", false, null);
            } catch (InvalidUsageException e) {
                ba.raiseEventFromDifferentThread(this, null, 0, eventName + "_error", false, new Object[] { e });
            }
        }
    });
}
 

max123

Well-Known Member
Licensed User
Longtime User
Thanks @Spavlyuk , seem same that mine apart these:
Java:
final Executor executor = Executors.newSingleThreadExecutor();
and this:
Java:
executor.execute(new Runnable() {

What is executor ?

PS: Now I've implemented the Complete event as your advices šŸ‘ and used events as @agraham advices.

In all functions that require some time I've used the same but without executor. and finally BA.submitRunnable:
Java:
public void ExtractZip(final String ZipPath, final String DestFolder, final String Password) {        
         
            Runnable r = new Runnable() {

                @Override
                public void run() {
 
                    // Initiate ZipFile object with the path/name of the zip file.
                    final ZipFile zipFile = new ZipFile(ZipPath);
                 
                    final String progEvt = eventName + "_progress";
                    final String errEvt = eventName + "_error";
                    final String compEvt = eventName + "_complete";
                 
                    int Percent = 0, oldPercent = 0;
       
                    try {
     
                        // Set true to monitor progress state
                        zipFile.setRunInThread(true);
                     
                        // Set password if password protected
                        if (Password.length() > 0)    {
                            try {
                                // if yes, then set the password for the zip file
                                zipFile.setPassword(Password.toCharArray());
                            } catch (Exception e) {
                                BA.Log("Zip4j: ExtractZip -> Cannot set Password for file " + ZipPath);
                                if (ba.subExists(errEvt)) {
                                    //ba.raiseEvent(this, errEvt, "ExtractZip", "Cannot set Password for file " + ZipPath);
                                    ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false,
                                 new Object[] {"ExtractZip", "Cannot set Password: " + e.getMessage(), ZipPath});                    
                                }
                                return;
                            }
                        }
                     
                        // Extracts all files to the path specified
                        zipFile.extractAll(DestFolder);
                     
                        // Get progress
                        if (ba.subExists(progEvt)) {
                            ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
                            //while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
                            while (progressMonitor.getState().equals(ProgressMonitor.State.BUSY)) {     // New syntax
                                Percent = progressMonitor.getPercentDone();
                                //BA.Log("Zip4j: OLD PERCENT: " + oldPercent + "  PERCENT: " + Percent);
                                if (oldPercent < Percent) {
     
                                   ba.raiseEventFromDifferentThread(this, null, 0, progEvt, false, new Object[] {"ExtractZip", Percent, ZipPath});  // <<== PROGRESS EVENT

                                   oldPercent = Percent;
                                }            
                            }
                                         
                           // HERE OPERATION IS DONE
                 
                            if(infoLog) BA.Log("Zip4j: ExtractZip -> Done extracted content of file " + ZipPath +
                                    " into Folder " + DestFolder + "  Result: " + getResultString(progressMonitor.getResult()));
                         
                            ba.raiseEventFromDifferentThread(this, null, 0, compEvt, false, new Object[] {"ExtractZip", ZipPath});   // <<== COMPLETE EVENT
                     
                  } else {
                            if(infoLog) BA.Log("Zip4j: ExtractZip -> Sub " + OriginalEventName +
                                    "_Progress does not exists. Use Async Mode to extract file " + ZipPath +
                                    " into Folder " + DestFolder);
                        }
                         
                    } catch (ZipException e) {
                        String err = e.getMessage().replace("net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: ", "");
                        err = err.replaceAll(":", "");
                        //BA.Log("Zip4j: ExtractZip -> " + err);
                        if (ba.subExists(errEvt)) {
                            ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", err, ZipPath});
                        }
                    } finally {
                        try {
                            zipFile.close();                    
                        } catch (IOException e) {
                            e.printStackTrace();
                            if (ba.subExists(errEvt)) {
                                ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", e.getMessage(), ZipPath});
                            }
                        };
                     
                   }
             
                }
            };
            BA.submitRunnable(r, this, 0);
        }
And on B4A side:
B4X:
' ------------------- LIBRARY EVENTS ----------------------

' Note that percentual can be 100 but maybe operation is not
' fully completed. Use the Complete event to check when operation
' is really completed.
Sub Zip_Progress (Method As String, Percent As Int, ZipPath As String)
    '  If Percent Mod 10 = 0 Then
    ProgressBar1.Progress = Percent
    lblPercent.Text = Percent & "%"
    Dim msg As String = $"${Method} -> Progress: ${Percent}%    BufferSize: ${Zip.BufferSize} Bytes    File: ${ZipPath}"$
    Label1.Text = msg
    Log(msg)
'    End If
End Sub

Sub Zip_Complete (Method As String, ZipPath As String)
    Dim elapsed As Long = DateTime.Now - StartTime
    Dim size As Long = File.Size(DirRoot, "THREEJS.zip")
     
    ToastMessageShow("AVG: " & NumberFormat2((size / (elapsed / 1000) / 1024 / 1024), 0, 2, 2, False) & " MBPS", False)
 
    Log(" ") : Log(Method & " -> Done!   Operation on Zip File: " & ZipPath)
    Log(" ") : Log($"Extracting Zip file of size ${size} Bytes required ${elapsed} milliseconds"$)
    Log("AVG: " & NumberFormat2((size / (elapsed / 1000)), 0, 0, 0, False) & _
    " BPS = " & NumberFormat2((size / (elapsed / 1000) / 1024 / 1024), 0, 2, 2, False) & _
        " MB Per Second")
    Log(" ")
    Log("Go to " & DirRoot & " folder to see results.")
    Label1.Text = "Done! - Go to " & DirRoot & " folder to see results." & CRLF & CRLF & _
        $"Extracting Zip file of size ${size} Bytes required ${elapsed} milliseconds"$ & CRLF & _
        "AVG: " & NumberFormat2((size / (elapsed / 1000)), 0, 0, 0, False) & _
        " BPS = " & NumberFormat2((size / (elapsed / 1000) / 1024 / 1024), 0, 2, 2, False) & _
        " MBPS" & CRLF & "BufferSize: " & Zip.BufferSize & " Bytes"
    Activity.Title = "Zip4j:  Done!"
End Sub

Sub Zip_Error (Method As String, Error As String, ZipPath As String)
    Log(Method & " -> ERROR OCCOURED: " & Error & "  File: " & ZipPath)
End Sub
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Yes, thanks,

The problem here is:
- If I do just an operation, so example unzip a big file, the version with Runnable works, every event will be fired, _Progress and _Complete, the _Error no yet tried
- because almost every command in the library use a Runnable, if in the main I put more than one operation, this start one by one and so start more threads (max 20 as Erel said), so more operations are executed on the same time and on main for sure we have callbacks event mixed. This is what really happen and I wont try to avoid.

Another problem is that if in the main I start more operations on the same Zip file, concurrent threads open it on same time, so I've found that some files are not created.

What I really want is this:
in the main put more operations so they are executed one by one and event fired (Progress, Error, Complete)
Only and Only when one operation finish the main call the next one, then wait to finish... and so .... so one operation at time. I don't want access maybe the same file at same time from more threads.

I studied it but not found a solution, the only one I found that seem to work is do not use more threads (operations on files should be done one by one), just the main and put DoEvents inside event subs. But this is not a good thing because DoEvents can create instability.

At the end what I want is just use the main thread but just do events to the gui and log, no other.
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
So, I've read about ExecutorService and seem it can do what I need, run a Runnable and wait until it thread finish. I' m right?
I've read this but confuse me a lot, I need to search a counterpart tutorial in my language (italian).
https://www.baeldung.com/java-executor-wait-for-threads
 

Spavlyuk

Active Member
Licensed User
Use an integer variable to count ongoing operations and raise an exception/event if it's above a specific limit.
Java:
int operations = 0;
public void ExtractZip(final String ZipPath, final String DestFolder, final String Password) {
    if (operations > 1) {
        throw new IllegalStateException("Thread limit exceeded.");
    }

    operations++;
    Runnable r = new Runnable() {
        @Override
        public void run() {
            // do stuff...
            operations--;
        }
    }
}

PS: You might need to use the synchronized keyword or use locks for thread safety, the sample above is untested.
 
Top