Kotlin Academy · Урок

withTimeout и withTimeoutOrNull

Ограничивайте время выполнения с помощью обёрток тайм-аута и обрабатывайте TimeoutCancellationException.

Урок 2 из 413 шагов

«withTimeout и withTimeoutOrNull» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Зачем нужны тайм-ауты

Корутины могут зависнуть на неопределённое время при работе с сетью или вводом-выводом. withTimeout отменяет блок, если он не завершается за указанное количество миллисекунд.

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        withTimeout(200) {
            delay(1000)  // simulates slow network
            println("This never prints")
        }
    } catch (e: TimeoutCancellationException) {
        println("Timed out!")
    }
}

Основы withTimeout

withTimeout(millis) { ... } выбрасывает TimeoutCancellationException (подкласс CancellationException), если выполнение блока превышает установленный предел.

import kotlinx.coroutines.*
suspend fun fetchData(): String {
    delay(100)
    return "data"
}
fun main() = runBlocking {
    val result = withTimeout(500) {
        fetchData()
    }
    println(result) // data
}

withTimeoutOrNull

withTimeoutOrNull возвращает null при превышении тайм-аута вместо выбрасывания исключения, поэтому тайм-ауты проще обрабатывать как обычный результат.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val result: String? = withTimeoutOrNull(200) {
        delay(1000)
        "done"
    }
    println(result ?: "Timeout — using default")
}

Тайм-аут с возвращаемым значением

При успешном выполнении обе функции возвращают значение последнего выражения в блоке.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val response = withTimeoutOrNull(500) {
        delay(100)
        mapOf("status" to 200, "body" to "OK")
    }
    println(response?.get("status")) // 200
}

Вложенные тайм-ауты

Сначала истекают внутренние тайм-ауты. Внешний тайм-аут срабатывает только в том случае, если внутренний не отменяет выполнение раньше — это удобно для разделения тайм-аутов отдельного запроса и всей операции.

import kotlinx.coroutines.*
fun main() = runBlocking {
    withTimeoutOrNull(1000) {       // global
        withTimeoutOrNull(200) {    // per-call
            delay(300)
            println("inner done")  // won't print
        } ?: println("Inner timed out")
        delay(100)
        println("outer still running")
    }
}

TimeoutCancellationException

TimeoutCancellationException является CancellationException, поэтому механизм корутин обрабатывает его как обычную отмену и не передаёт родительской области действия.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            withTimeout(100) { delay(1000) }
        } catch (e: TimeoutCancellationException) {
            println("Caught in child: ${e.message}")
        }
    }
    job.join()
    println("Parent still running: ${isActive}")
}

Очистка ресурсов при тайм-ауте

Используйте finally внутри withTimeout, чтобы освобождать ресурсы даже при истечении времени.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val result = withTimeoutOrNull(150) {
        try {
            println("Opening resource")
            delay(300)
            "result"
        } finally {
            println("Closing resource") // always runs
        }
    }
    println("Result: $result")
}

Повторная попытка с тайм-аутом

Объединяйте тайм-аут с логикой повторных попыток: выполняйте операцию с отдельным тайм-аутом для каждого attempt и повторяйте её при получении результата null.

import kotlinx.coroutines.*
suspend fun tryFetch(attempt: Int): String? = withTimeoutOrNull(200) {
    delay(if (attempt < 3) 300L else 100L) // fails first 2 attempts
    "success on attempt $attempt"
}
fun main() = runBlocking {
    var result: String? = null
    var attempt = 1
    while (result == null) {
        result = tryFetch(attempt++)
    }
    println(result)
}

withTimeout в ViewModel

В Android ViewModels оборачивайте вызовы репозитория в withTimeoutOrNull внутри viewModelScope.launch, чтобы показывать состояние ошибки при медленных ответах.

import kotlinx.coroutines.*
// Pseudocode pattern:
suspend fun loadUser(): String = withTimeoutOrNull(3000) {
    // repo.getUser()
    delay(100)
    "Alice"
} ?: "Timeout — using cached data"
fun main() = runBlocking { println(loadUser()) }

Оговорка о точности

withTimeout зависит от диспетчера корутин. В тестах с использованием TestCoroutineScheduler время виртуальное, и его можно продвигать вручную.

import kotlinx.coroutines.*
// In unit tests with runTest:
// runTest {
//     withTimeout(1000) {
//         delay(999)  // virtual time — completes instantly
//         println("done")
//     }
// }
fun main() = runBlocking {
    println("Use runTest for virtual-time timeout testing")
}

Как выбрать между двумя функциями

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Mandatory: throw on timeout
    // withTimeout(500) { criticalOp() }

    // Optional: null on timeout
    val cached = withTimeoutOrNull(50) {
        delay(200); "fresh"
    } ?: "stale"
    println(cached)
}

Быстрая проверка

Что возвращает withTimeoutOrNull, если выполнение блока превышает ограничение по времени?

Итоги

withTimeout выбрасывает исключение при тайм-ауте, а withTimeoutOrNull возвращает значение null. Обе функции кооперативно отменяют блок и выполняют блоки finally. Используйте finally для очистки ресурсов, а withTimeoutOrNull — когда тайм-аут является ожидаемым результатом.

Можно начать бесплатно

Изучай Kotlin с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
51
Уроки
203

Часто задаваемые вопросы

Урок «withTimeout и withTimeoutOrNull» бесплатный?

Да — полный текст урока «withTimeout и withTimeoutOrNull» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «withTimeout и withTimeoutOrNull»?

Ограничивайте время выполнения с помощью обёрток тайм-аута и обрабатывайте TimeoutCancellationException. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «withTimeout и withTimeoutOrNull»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Кооперативная отмена: isActive и ensureActive
  2. withTimeout и withTimeoutOrNull
  3. Очистка с finally и NonCancellable
  4. Распространение отмены в иерархиях сопрограмм
← Назад к Kotlin Academy