3

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

azizbekian
  • 60,783
  • 13
  • 169
  • 249
drmrbrewer
  • 11,491
  • 21
  • 85
  • 181

2 Answers2

4

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.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • So if execution of `functionA()` is really delayed, it's still possible that `functionA()` will be executed at "now + 1999" whereas `functionB()` is executed at "now + 2000"? In theory? And yes I am actually now using the approach you suggest, which is to call `functionB()` from within `functionA()`... I was still curious to know the answer to the question. – drmrbrewer Apr 02 '20 at 12:25
  • 1
    If `MessageQueue` is full with other messages before `functionA()` and empty after `functionA()` then in theory yes, that's possible (not sure on this one, just making a deduction based on the principles of how these components are working). Honestly, I cannot see how this is possible in a real word. – azizbekian Apr 02 '20 at 14:21
3

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154