Что такое Coroutines и зачем они нужны?
Coroutines (сопрограммы) — это мощный инструмент для асинхронного программирования в Kotlin, который позволяет писать неблокирующий код в императивном стиле. В отличие от традиционных потоков (threads), корутины являются легковесными: вы можете запускать тысячи корутин на одном потоке без значительного потребления памяти.
Основная задача Coroutines — упростить работу с асинхронными операциями: сетевыми запросами, чтением из базы данных, файловыми операциями и другими задачами, которые требуют ожидания. Вместо того чтобы использовать сложные callback-и или реактивные стримы, вы пишете последовательный код, который приостанавливается во время ожидания и возобновляется после завершения операции.
Библиотека Coroutines является частью экосистемы Kotlin и официально поддерживается JetBrains. Она широко используется в Android-разработке, серверных приложениях (Ktor, Spring WebFlux) и даже в desktop-приложениях.
Установка Coroutines
Поскольку Coroutines — это не отдельная библиотека для установки через pip или npm, а часть экосистемы Kotlin, её подключение зависит от вашего менеджера сборки.
Gradle (Kotlin DSL)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
// Для Android или использования Dispatchers.Main
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}
Gradle (Groovy DSL)
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
}
Maven
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.9.0</version>
</dependency>
Быстрый старт: первый пример
Давайте напишем простую программу, которая запускает корутину, делает паузу и выводит сообщение:
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Начало программы: ${Thread.currentThread().name}")
launch {
delay(1000L) // Приостанавливаем корутину на 1 секунду (не блокируя поток)
println("Мир! (из корутины: ${Thread.currentThread().name})")
}
println("Привет, (из main: ${Thread.currentThread().name})")
delay(500L) // Ждём, чтобы корутина успела выполниться
println("Конец программы")
}
Вывод:
Начало программы: main @coroutine#1
Привет, (из main: @coroutine#1)
Мир! (из корутины: @coroutine#2)
Конец программы
Обратите внимание: delay() не блокирует поток, а приостанавливает только текущую корутину. Поток main остаётся свободным для других задач.
Основные методы и функции Coroutines
1. runBlocking
Что делает: Запускает новую корутину и блокирует текущий поток до её завершения. Используется как мост между обычным синхронным кодом и миром корутин (обычно в main-функции или тестах).
Параметры:
context: CoroutineContext— контекст корутины (по умолчанию EmptyCoroutineContext)block: suspend CoroutineScope.() -> T— suspend-блок, который будет выполнен
Пример:
fun main() {
val result = runBlocking {
delay(1000L)
"Готово!"
}
println(result) // Выведет: Готово!
}
2. launch
Что делает: Запускает новую корутину, которая не возвращает результат (аналог fire-and-forget). Возвращает объект Job, который можно использовать для отмены или ожидания.
Параметры:
context: CoroutineContext— контекст (например, Dispatchers.IO)start: CoroutineStart— опция старта (DEFAULT, LAZY, ATOMIC, UNDISPATCHED)block: suspend CoroutineScope.() -> Unit— выполняемый блок
Пример:
fun main() = runBlocking {
val job = launch {
delay(1000L)
println("Задача выполнена")
}
println("Ожидаем...")
job.join() // Ждём завершения корутины
println("Готово")
}
3. async / await
Что делает: Запускает корутину, которая возвращает результат через объект Deferred. Метод await() приостанавливает текущую корутину до получения результата. Идеально для параллельного выполнения независимых задач.
Параметры: Аналогично launch, но block возвращает T.
Пример:
fun main() = runBlocking {
val deferred1 = async {
delay(1000L)
10
}
val deferred2 = async {
delay(500L)
20
}
val sum = deferred1.await() + deferred2.await()
println("Сумма: $sum") // Выведет: Сумма: 30 (через ~1 секунду)
}
4. withContext
Что делает: Позволяет переключить контекст выполнения корутины (например, с Main на IO) и вернуть результат. Не создаёт новую корутину, а временно меняет контекст текущей.
Параметры:
context: CoroutineContext— новый контекстblock: suspend CoroutineScope.() -> T— блок для выполнения в новом контексте
Пример:
fun main() = runBlocking {
val result = withContext(Dispatchers.IO) {
// Тяжёлая операция в фоновом потоке
Thread.sleep(1000L)
"Данные из БД"
}
println(result) // Выведет: Данные из БД
}
5. delay
Что делает: Приостанавливает текущую корутину на указанное время (в миллисекундах) без блокировки потока. Это не Thread.sleep() — поток остаётся свободным для других корутин.
Параметры:
timeMillis: Long— время задержки в миллисекундах
Пример:
fun main() = runBlocking {
println("Старт")
delay(2000L)
println("Прошло 2 секунды")
}
6. Job.cancel() и Job.join()
Что делает: cancel() отменяет выполнение корутины. join() ожидает её завершения. Комбинация cancelAndJoin() делает оба действия сразу.
Параметры: cancel() может принимать CancellationException с сообщением.
Пример:
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("Работаю: $i")
delay(500L)
}
}
delay(1300L)
println("Отменяем!")
job.cancelAndJoin()
println("Отменено")
}
7. CoroutineScope
Что делает: Определяет область видимости для корутин. Все корутины, запущенные внутри scope, автоматически отменяются при его завершении. Используется для структурированной конкурентности.
Параметры: Принимает CoroutineContext.
Пример:
class MyActivity {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun loadData() {
scope.launch {
val data = withContext(Dispatchers.IO) { fetchData() }
showData(data)
}
}
fun onDestroy() {
scope.cancel() // Отменяем все корутины при уничтожении Activity
}
}
8. Dispatchers
Что делает: Определяет, на каком пуле потоков будет выполняться корутина. Основные диспетчеры:
Dispatchers.Main— главный поток (UI в Android)Dispatchers.IO— для операций ввода-вывода (сеть, БД)Dispatchers.Default— для CPU-интенсивных задачDispatchers.Unconfined— не привязан к конкретному потоку
Пример:
fun main() = runBlocking {
launch(Dispatchers.IO) {
println("Работаю в IO: ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Работаю в Default: ${Thread.currentThread().name}")
}
}
9. supervisorScope
Что делает: Создаёт дочернюю область, где отмена одной корутины не влияет на другие. В отличие от coroutineScope, где исключение в одной дочерней корутине отменяет все остальные.
Параметры: Принимает suspend-блок.
Пример:
fun main() = runBlocking {
supervisorScope {
launch {
delay(100L)
throw RuntimeException("Ошибка в корутине 1")
}
launch {
delay(200L)
println("Корутина 2 завершена успешно")
}
}
// Выведет: Корутина 2 завершена успешно (несмотря на ошибку в первой)
}
10. yield
Что делает: Приостанавливает текущую корутину и даёт возможность другим корутинам выполниться. Полезно для кооперативной многозадачности.
Параметры: Нет.
Пример:
fun main() = runBlocking {
launch {
repeat(3) {
println("Корутина A: $it")
yield() // Даём шанс другим корутинам
}
}
launch {
repeat(3) {
println("Корутина B: $it")
yield()
}
}
}
Реальный практический пример: загрузка данных из сети
Представим, что мы пишем приложение для Android, которое загружает список пользователей из API и отображает его:
import kotlinx.coroutines.*
import java.net.URL
// Имитация сетевого запроса
suspend fun fetchUsers(): List<String> {
return withContext(Dispatchers.IO) {
// Реальный запрос к API
val response = URL("https://jsonplaceholder.typicode.com/users").readText()
// Парсинг (упрощённо)
response.split("\"name\":\"")
.drop(1)
.map { it.substringBefore("\"") }
}
}
// Имитация сохранения в БД
suspend fun saveToDatabase(users: List<String>) {
withContext(Dispatchers.IO) {
delay(500L) // Имитация записи
println("Сохранено ${users.size} пользователей в БД")
}
}
fun main() = runBlocking {
println("Начинаем загрузку...")
// Загружаем данные параллельно
val usersDeferred = async { fetchUsers() }
val cachedDeferred = async {
delay(300L)
listOf("Кэшированный пользователь")
}
val users = usersDeferred.await()
val cached = cachedDeferred.await()
println("Загружено ${users.size} пользователей из сети")
println("Загружено ${cached.size} из кэша")
// Сохраняем в фоне
launch(Dispatchers.IO) {
saveToDatabase(users + cached)
}
println Также в библиотеке
Redis клиент для PHP: Predis и PhpRedis – подключение и примеры
WorkManager для Kotlin: полное руководство с примерами
Puppeteer для JavaScript (Node.js): полное руководство с примерами
Отправка email на Java: библиотека Jakarta Mail (JavaMail)
Apache Commons для Java: полное руководство с примерами