Damian Petla
Damian Petla

Reputation: 9103

How to let withTimeoutOrNull return null but finish code in the block

I need my code to run a block and return value after 1 second in case timeout but let it finish the job. I managed to implement something that works but IDE suggest replacing async with withContext(DefaultDispatcher) but it's not working the same.

So my question is how to make it work without IDE warnings. I am new to Kotlin Coroutines so I might be missing something here.

@Test
fun how_timeout_with_null_works() = runBlocking<Unit> {
    val time = measureTimeMillis {
        println("Start test")
        val result = withTimeoutOrNull(1, TimeUnit.SECONDS) {
            async { doSomeHardWork() }.await()
        }
        println("End test $result")
    }
    println("Time $time")
    delay(3000)
}

private suspend fun doSomeHardWork(): String {
    println("start hard work")
    Thread.sleep(2000)
    print("end hard work")
    return "[hard work done]"
}

Upvotes: 3

Views: 4556

Answers (1)

Roman  Elizarov
Roman Elizarov

Reputation: 28698

IDE gives a warning in this case because async(ctx) { ... }.await() is usually a mistake and withContext(ctx) { ... } usually better reflects the original intent of the author of the code.

In case of your code, your intent is different. Your intent is to await for 1 second, without constraining your doSomeHardWork code. However, the structure of your code does not reflect your intent. You've wrapped the whole block into withTimeout and put doSomeHardWork inside of it, while your intent was only to do a time-limited wait for it. So, if you rewrite your code in the way where the structure of your code matches your intent, it will work without any warnings:

val work = async { doSomeHardWork() } // start work
val result = withTimeoutOrNull(1, TimeUnit.SECONDS) { work.await() } // wait with timeout

If you happen to need this pattern of code more than once, then you can define yourself a handy extension:

suspend fun <T> Deferred<T>.awaitWithTimeout(time: Long, unit: TimeUnit): T? =
    withTimeoutOrNull(time, unit) { await() }

And then write even nicer code that reflects your intent:

val result = async { doSomeHardWork() }.awaitWithTimeout(1, TimeUnit.SECONDS)

Be careful, though. These coroutines that you start with async will continue running after the wait had timed out. This can easily lead to resource leaks, unless you take steps to limit the number of those concurrently running background jobs.

Upvotes: 7

Related Questions