Repeat a task within a duration with delay

10,174

Solution 1

so what you are doing here is simulating progress. Ideally, there would be some way of checking the actual progress of your bar, and updating it, and when it is done, ending. But, if this is not possible, then ya, simulation is your choice.

So, with coroutines we are dealing with a threaded environment, and within that, we have our coroutines which need to be continued when the hand over control of execution. In your implementation, this happens at the delay call. For this reason, it is very difficult to guarantee that your coroutine will complete in your desired time. All delay can do is say that it will not resume before "at least" the specified time has elapsed, and probably quite often, more time would have elapsed, not the exact time.

So, how do we get this to execute in as close to your desired time frame as possible? What we need to do is drop the repeat, and rather check on the elapsed time to decide if we finish. Here is a rough implementation that will hopefully help.

class Bar(val barLength: Int = 1000) {
    var progress = 0
}

suspend fun simulateProgress(bar: Bar, job: Job, totalDurationMillis: Long, incrementsMills: Long): Job {
    var startTime = System.currentTimeMillis()
    return CoroutineScope(Dispatchers.Default + job).launch {
        var totalElapsed = 0L
        while (totalElapsed < totalDurationMillis) {
            totalElapsed = System.currentTimeMillis() - startTime
            val progressRatio = totalElapsed.toDouble()/totalDurationMillis.toDouble()
            bar.progress = (progressRatio * bar.barLength.toDouble()).toInt()
            delay(incrementsMills)
        }
        println("Elapsed: $totalElapsed, Progress: ${bar.progress}")
    }
}

fun main() = runBlocking {
    val job = Job()
    val bar = Bar()
    val progressJob = simulateProgress(bar, job, 6000, 10)
    progressJob.join()
} 

Solution 2

I would do it with something like this:

withTimeout(1300L) {
    repeat(1000) { i ->
        println("Blip Blop $i ...")
        delay(500L)
    }
}

For more examples see official doc: https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html

Solution 3

Coroutine does not provide precise timing. If the processor is busy running other stuff at the same time, coroutines can be easily delayed. Use the Timer Class for precise timing. Here is an example. I log the time every seconds and cancel the timer after 6 seconds. The results are only off by a few milliseconds.

    var startTime = 0L
    val timer : Timer = Timer()
    val task = object : TimerTask()
    {
        var lastTime = 0L
        override fun run() {
            val now = System.currentTimeMillis()
            if(now/1000 > lastTime/1000 )
            {
                Log.d("timer","total time= ${now - startTime}")
                lastTime = now
            }
            if(now - startTime >= 6000)
            {
                timer.cancel()
            }
    }
    startTime = System.currentTimeMillis()
    timer.scheduleAtFixedRate(task,0,6)
Share:
10,174
Hayton Leung
Author by

Hayton Leung

Updated on June 18, 2022

Comments

  • Hayton Leung
    Hayton Leung almost 2 years

    I have to move a progress bar within a time frame, for example within 6s. I am using coroutines and the "repeat" function. The code executes except that the total execution time is not as specified. Below is my code.

    val progressJob = Job()
    var startTime = 0L
    CoroutineScope(Dispatchers.Default + progressJob).launch {
        startTime = System.currentTimeMillis()
        repeat(1000) {
            progressBar.progress += 1
            delay(6)
        }
        Log.d(TAG, "total time= ${System.currentTimeMillis() - startTime}")
    }
    

    I am expecting "total time" would be 6000, but I am getting values greater than 6000 by at least 500.

    Basically I just want to repeatedly increment the progress bar within a time frame, and I am not using animation because of performance issue.

    Is there anything I am missing?

  • Hayton Leung
    Hayton Leung about 5 years
    Thank you for your explanation. I didn't notice the for loop inside delay until you point it out. I guess this is where the extra time comes from
  • Hayton Leung
    Hayton Leung about 5 years
    For some other requirements I will have to stick with coroutines, but thanks for your suggestion.
  • Hayton Leung
    Hayton Leung about 5 years
    Dropping the repeat and replace with while is the key. On average the total time is 601X, which is close enough for my use case. Thanks a lot.
  • Laurence
    Laurence about 5 years
    Just for info - you should not use Thread tools when you are achieving concurrency with coroutines. It sort of defeats the whole point.
  • Laurence
    Laurence about 5 years
    @HaytonLeung - no problem.