Kotlinx.coroutines: Provide Scheduler For Running A Task Periodically

Created on 24 Oct 2019  Â·  10Comments  Â·  Source: Kotlin/kotlinx.coroutines

With the KotlinX Coroutines library there is no scheduler for running a task periodically (eg run a task every 5 seconds). On the JVM side for threads the Timer and TimerTask classes cover this use case, however it doesn't work with coroutines, only covers the JVM side, and introduces a bit of ceremony (have to extend TimerTask which is a bit heavy).

Most helpful comment

The above code is again same as one posted by JakeWharton, it will be inaccurate as the time pass by since action may take some time and because it suspending, it'll suspend the while loop as well and next time action will trigger will be after a delay of (nextRunTimeInMillis+timeTakenByActionRanPreviously).

I tried myself and I thinked quite simple:

suspend fun scheduleRepeatedly(delayTimeMillis: Long, action: suspend CoroutineScope.() -> Unit) = coroutineScope {
    while (true) {
        delay(delayTimeMillis)
        launch { action() }
    }
}

It will still be somewhat inaccurate as time it runs -> infinity. Because launching still takes a very small amount of time. But this will be far better than inaccuracy caused by runtime of action.

I also like Kotlin to implement such functionality to the std-lib because what I see is Coroutines uses time to manage coroutine dispatches rather than delay time (they convert delay time to currentTime+delayTime then run it by periodic comparing current time with time to dispatch).

All 10 comments

I'm not sure there needs to be a primitive for this since you can do

while (true) {
  delay(Duration.ofSeconds(5))
  task()
}

to accomplish this. Plus it can scale to complex branching an asymmetric delays and all kinds of more advanced scheduling features.

@JakeWharton - What you did in the example is create a slightly enhanced event loop, which doesn't solve the problem of running a task periodically. Assuming that task is a function, and the code is running in the main function there would be six issues with the example:

  1. The task function BLOCKS the main thread
  2. Duration is fixed in place (aka "magical number") in the delay coroutine
  3. The function used for running the task is fixed in place
  4. Cannot cancel the loop
  5. No clear way to easily manage resources (eg what happens if the task function throws an exception?)
  6. If the current task takes longer to run than the specified duration then another task cannot be executed until the previous one is completed, therefore the loop is blocked (in a deadlocked state)
  1. The executing dispatcher is undefined, but as task is presumably suspending you're not only not blocking, but you're free to withContext and jump around all you want.
  2. So make it an argument to a function
  3. So make it an argument to a function
  4. Cancelation is built into coroutines
  5. The same thing that happens in a normal function. If you have a resource, use it.
  6. Concurrent execution was not a specified requirement, but you can just launch each task to achieve that.

The point of my comment was that you don't need some heavyweight abstraction provided by the library to accomplish your goal. It's trivially done yourself.

Cancellation is built into CoroutineScope, which is accessed via the isActive property, eg:

// ...
while (isActive) {
    delay(Duration.ofSeconds(5))
    task()
}

It does not, because delay is cancelable and it is your responsibility to make sure the code invoked by task is also cancelable. No significant time is spent in the while (true) loop such that it benefits you to check isActive.

The point of my comment was that you don't need some heavyweight abstraction provided by the library to accomplish your goal. It's trivially done yourself.

You have made the assumption that the abstraction would be heavy weight, when the solution would likely be a lightweight abstraction (eg a function). If the solution was trivial then there would be no need for the KotlinX Coroutines Core library to include the withTimeout function for instance.

Timeouts are quite complicated so I don't really see the similarity.

On Thu, Oct 24, 2019 at 10:11 PM Nick Apperley notifications@github.com
wrote:

The point of my comment was that you don't need some heavyweight
abstraction provided by the library to accomplish your goal. It's trivially
done yourself.

You have made the assumption that the abstraction would be heavy weight,
when the solution would likely be a lightweight abstraction (eg a
function). If the solution was trivial then there would be no need for the
KotlinX Coroutines Core library to include the withTimeout function
https://github.com/Kotlin/kotlinx.coroutines/blob/a70022d6e7d9aa5d8fe27a2c46e987a2e8b85c21/kotlinx-coroutines-core/common/src/Timeout.kt#L28
for instance.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Kotlin/kotlinx.coroutines/issues/1632?email_source=notifications&email_token=AAAQIEMXKLJYT5LZQBPGWB3QQJIWZA5CNFSM4JEMXAUKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECG6PLA#issuecomment-546170796,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAAQIEOHJRFZJHB2MOGLMHDQQJIWZANCNFSM4JEMXAUA
.

There are some similarities shared with the withTimeout function which are that there is a form of scheduling being done, a number of factors need to be taken into account, a task is being run which is based on timing, and numerous things can go wrong (multiple paths of failure that can easily introduce problems that aren't trivial to fix) which wouldn't be properly solved by developing and using a code snippet. Put it this way if scheduling tasks to run based on a timer were trivial in Java then there wouldn't be a need for the Timer and TimerTask classes.

By having the withTimeout function present in the KotlinX Coroutines library it shows that there are some schedulers already included in the library.

I just ran into this also and I think it would be useful as simple functions, mostly because Java itself offers functionality for this and Kotlin also provides some extension functions for Timer, but none of this is based on coroutines.

This is a very simple implementation I just came up with:

suspend inline fun scheduleRepeating(
    startFrom: ZonedDateTime,
    schedule: Schedule,
    intervalInMillis: Long,
    crossinline action: () -> Unit
) = schedule.next(startFrom)
    .let { nextSchedule -> Duration.between(startFrom, nextSchedule).toMillis() }
    .letInCoroutine { nextRunTimeInMillis ->
        delay(nextRunTimeInMillis)
        while(isActive) {
            action()
            delay(intervalInMillis)
        }
    }

But it does depend on https://github.com/shyiko/skedule and letInCoroutine is this:

suspend inline fun <T, R> T.letInCoroutine(
    crossinline block: suspend CoroutineScope.(T) -> R
): R = coroutineScope {
    block(this@letInCoroutine)
}

The above code is again same as one posted by JakeWharton, it will be inaccurate as the time pass by since action may take some time and because it suspending, it'll suspend the while loop as well and next time action will trigger will be after a delay of (nextRunTimeInMillis+timeTakenByActionRanPreviously).

I tried myself and I thinked quite simple:

suspend fun scheduleRepeatedly(delayTimeMillis: Long, action: suspend CoroutineScope.() -> Unit) = coroutineScope {
    while (true) {
        delay(delayTimeMillis)
        launch { action() }
    }
}

It will still be somewhat inaccurate as time it runs -> infinity. Because launching still takes a very small amount of time. But this will be far better than inaccuracy caused by runtime of action.

I also like Kotlin to implement such functionality to the std-lib because what I see is Coroutines uses time to manage coroutine dispatches rather than delay time (they convert delay time to currentTime+delayTime then run it by periodic comparing current time with time to dispatch).

Was this page helpful?
0 / 5 - 0 ratings