Как разрешить withTimeoutOrNull возвращать ноль, но завершать код в блоке

Мне нужен мой код для запуска блока и возврата значения через 1 секунду в случае тайм-аута, но пусть он завершит работу. Мне удалось реализовать что-то, что работает, но IDE предлагает заменить async с withContext(DefaultDispatcher) но это не работает так же.

Поэтому мой вопрос заключается в том, как заставить его работать без предупреждений IDE. Я новичок в Kotlin Coroutines, так что я могу что-то здесь упустить.

@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]"
}

1 ответ

Решение

IDE выдает предупреждение в этом случае, потому что async(ctx) { ... }.await() обычно это ошибка и withContext(ctx) { ... } обычно лучше отражает первоначальные намерения автора кода.

В случае вашего кода ваше намерение другое. Ваше намерение состоит в том, чтобы ждать в течение 1 секунды, не ограничивая doSomeHardWork код. Однако структура вашего кода не отражает ваших намерений. Вы завернули весь блок в withTimeout и положи doSomeHardWork внутри, в то время как ваше намерение состояло лишь в том, чтобы сделать это в течение ограниченного времени. Итак, если вы переписываете свой код так, чтобы его структура соответствовала вашим намерениям, он будет работать без каких-либо предупреждений:

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

Если вам понадобится этот шаблон кода более одного раза, тогда вы можете определить себе удобное расширение:

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

А затем напишите еще более приятный код, который отражает ваши намерения:

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

Будьте осторожны, хотя. Эти сопрограммы, с которых вы начинаете async будет продолжать работать после истечения времени ожидания. Это может легко привести к утечке ресурсов, если вы не предпримете шаги по ограничению количества одновременно работающих фоновых заданий.

Другие вопросы по тегам