Delay a task


#1

I would like to be able to write this code
delay(0.5f) { foo() }
Where the code inside the “delay” body will be executed 0.5 seconds later

The problem is that if this above line of code is re-executed BEFORE those 0.5 seconds, the timer needs to be reset, so the code inside the “delay” body will only be executed if more than 0.5 seconds passes between each call. For example, in

while (true) {
     delay(0.5f) { foo() }
}

The foo method must never be called because there is not enough time between each call


#2

What platform you are talking about. In JVM there are Shceduled executors which do just what you need in asynchronous non-blocking way.

In general, if you have delay function delay() which is in fact different for different platforms (in JVM it would be Thread.sleep(), in coroutines framework delay()). you probably want to write something like:

fun delay(time: Int, action: ()->Unit){
    delay(time)
    action()
}

#3

I can’t test right now but I’m pretty sure that with

fun delay(time: Int, action: ()->Unit){
    delay(time)
    action()
}

It will not act exactly as I want, remember that with something like

repeat(100) { delay(0.5f) { foo() } }

the “foo()” function should only be executed once because every calls of this line of code, but the last one must be cancelled

edit : And that’s what I want, I don’t want the “foo()” function to be executed 100 times


#4

Sorry, I don’t really understand what you are saying. You want for some action to be conditionally executed only if some specific time passed since previous execution? In that case you need to store the time of previous execution somewhere and check if current time differs from the last one more than that fixed interval. It is really ugly solution to use in production, better to use timers or schedulers. I am not aware if something like that is available for JS, But probably it is.


#5

I’ll give a simple example of how it should work :slight_smile:

Let’s say you have a button, when you press that button, a task is scheduled to be executed one second later.
The thing is that, I want that if you click that button again while the task is already scheduled (so before the one second happened), then it doesn’t create a new task, it just resets the first task’s schedule

I want to know if there is a clean, one line solution that do this

btw, I work on the JVM


#6

There is probably a better way, but this works:

class SingleSchedulePool {

	val executorService = Executors.newScheduledThreadPool(1)
	var future: ScheduledFuture<*>? = null

	fun delay(delay: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, body: (() -> Unit)) {
		future?.cancel(false)

		val runnable = Runnable { body() }

		future = executorService.schedule(runnable, delay, timeUnit)
	}

	fun shutdown() = executorService.shutdown()

}

fun main(args: Array<String>) {
	val pool = SingleSchedulePool()

	pool.delay(0, TimeUnit.SECONDS) { println("test 1") }
	Thread.sleep(100)
	pool.delay(2, TimeUnit.SECONDS) { println("test 2") }
	pool.delay(2, TimeUnit.SECONDS) { println("test 3") }
	pool.delay(2, TimeUnit.SECONDS) { println("test 4") }

	pool.shutdown()
}

#7

OK, then you should not do it in a way you proposed. On JVM the best way to do so is to create a Future instance or its analogue from coroutines. Like that:

val executor: ExecutorService = ...
val future: Future<*>? = null

fun pressButton(){
  if(future == null || future.isDone()){
    executor.submit{<your action here>}
  }
}

Usually UI framework has some way to launch a task not on UI thread and return a future. In tornadofx it is runAsync (look here for details).


#8

He wants a resetable timer. java.util.Timer could be a help here but you’d have to create a wrapper (the class doesn’t allow you to look at the internals or change the timer - you need to remember the action and when resetting is needed call cancel and create a new timer with the new timeout - but only if the timer hasn’t fired yet).

A better solution would be to use Thread.wait to implement it manually as I’m not sure you can implement the other solution without race conditions. It’s a fairly simple piece of code.


#9

I’ve done this in the past like

var timer: Job

fun restartTimer(){
  timer.cancel()
  timer = launch(CommonPool){ delay(0.5f); foo() }
}

Not very elegant, and not thread-safe, but it works.