Maintaining a time gap with post vs postDelayed

Viewed 264

There are two functions I need to run with a minimum time gap between them. For reasons beyond the scope of this question, at first I was trying to control the timing from a process running in the webview (via a JavascriptInterface):

webView.post(() -> functionA());
// ... wait 2 secs in javascript and then...
webView.post(() -> functionB());

Whilst this worked fine most of the time, for one user in particular it seemed that the two functions were sometimes running immediately after each other (still in the right order, but just without the time gap).

On reflection, this is understandable, if it is the case that posting a runnable to a handler will just put them in a queue, without any guarantee to maintain the relative timing based on when they were placed in the queue.

So, if that is the case, then my new strategy is to forget about controlling the timing from the javascript running in the webview, and just control it in the Java directly.

So the question is... does using postDelayed() as follows guarantee at least a minimum time gap between the two functions being run?

webView.post(() -> functionA());
webView.postDelayed(() -> functionB(), 2000);

I feel that it ought to have the desired effect, but am wary that maybe it amounts to the same as what I was doing... putting functionB into the queue 2 secs after functionA, with no guarantee that they will actually maintain that time gap between them

2 Answers

What post() and postDelayed() are doing, is that they are adding a Message into the MessageQueue, which Looper loops on. So, the action inside post() won't be executed synchronously, instead it will be executed at a later point of time.

webView.post(() -> functionA()) will result in adding an action, that would be run in some time in future when webView would pass its measure-layout-draw methods. For the sake of example let's assume this will take 15ms.

webView.postDelayed(() -> functionB(), 2000) will result in adding an action, that would be run in roughly 2s starting from now. So, as a matter of fact, functionA and functionB aren't guaranteed to be called in 2000ms interval (and most possibly they won't be), because functionA() was executed at "now + 15" whereas functionB() was executed at "now + 2000".

Instead, if you have that strict requirement, maybe instead of using postDelayed() you should be using one of postAtTime() overloads?

Consider an approach: perform webView.post(() -> functionA()) and inside functionA() schedule a new runnable to be executed in 2000s as such Handler().postDelayed(2000, someRunnable). I think this might be a working approach for your use-case.

Not sure if you mean to be asking a Java Question or a JavaScript question.

Java Executors framework

If Java, the solution is simple. Java provides the Executors framework for running tasks on threads. This includes scheduling tasks to run after an initial delay. (See tutorial by Oracle.)

To have a pair of tasks run with a time gap between them, schedule the first to run immediately. Then, on the same scheduled executor service, schedule the second task to run after a delay.

Example

Define your tasks as either Runnable or Callable objects.

Runnable task = 
    () -> { 
        System.out.println( "Task is running. " + Instant.now() ); 
    }
;

Use Executors utility class to get a single-threaded scheduled executor service.

ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor() ;

Schedule each of your tasks.

In this example, we start the first task immediately on the background thread.

ses.schedule( task1 , 0 , TimeUnit.SECONDS ) ;  // Runs immediately, in the background on another thread. 

The second task will begin in about 45 seconds, also on the background thread.

ses.schedule(               // Tell the executor what you want to run, and when you want it run on your behalf in a background thread.
    task2 ,                 // Define task to be executed as a `Runnable`.
    45 ,                    // Count of time to wait.
    TimeUnit.SECONDS        // Specify the granularity of time used in previous argument.
) ; 

Catch exceptions

Tip: Be sure to wrap the work in your task with a general try-catch. If any uncaught exception (or error) were to bubble up all the way to the executor service, the service halts. Any future scheduled tasks will not be run. This happens silently. For more info, see my Answer on a related question.

Shut down your thread pool

When all your tasks are done, or when your app is terminating, shutdown the scheduled executor service by calling one of its shutdown methods. Otherwise the thread(s) backing your executor service may continue running indefinitely.

Jakarta Concurrency utilities

If you are build a web app, your Jakarta EE app server may support Jakarta Concurrency to further simplify this coding and automatically shutdown your executor service’s backing thread pool.

Related