← Назад к вопросам

В чем разница между launch, async и runBlocking?

2.0 Middle🔥 142 комментариев
#Опыт и софт-скиллы

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Различия между launch, async и runBlocking в Kotlin Coroutines

В Kotlin Coroutines эти три строительных блока представляют различные подходы к запуску сопрограмм с разными семантиками и целями использования. Давайте детально рассмотрим каждый из них.

1. runBlocking - Блокирующий запуск

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

import kotlinx.coroutines.*

fun main() {
    println("Начало работы в потоке: ${Thread.currentThread().name}")
    
    runBlocking {
        // Этот блок выполняется в runBlocking контексте
        println("Внутри runBlocking: ${Thread.currentThread().name}")
        delay(1000) // Приостановка на 1 секунду
        println("Завершение runBlocking")
    }
    
    println("Эта строка выполнится ТОЛЬКО после завершения runBlocking")
    // Вывод:
    // Начало работы в потоке: main
    // Внутри runBlocking: main
    // Завершение runBlocking
    // Эта строка выполнится ТОЛЬКО после завершения runBlocking
}

Ключевые характеристики runBlocking:

  • Блокирует текущий поток до завершения
  • 🔧 Используется в main-функциях, тестах, или когда нужно временно перейти к блокирующему стилю
  • 📝 Не должен использоваться в production коде на Android в UI-потоке
  • ⚠️ Нарушает основную идею асинхронного неблокирующего выполнения

2. launch - Запуск без возвращаемого значения

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

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Запуск родительской сопрограммы")
    
    val job = launch {
        // Эта сопрограмма выполняется асинхронно
        println("Старт дочерней сопрограммы: ${Thread.currentThread().name}")
        delay(1500)
        println("Завершение дочерней сопрограммы")
    }
    
    println("Родительская сопрограмма продолжает работу параллельно")
    delay(500)
    println("Родительская сопрограмма все еще работает...")
    
    job.join() // Ожидание завершения дочерней сопрограммы
    println("Все сопрограммы завершены")
}

Ключевые характеристики launch:

  • Не блокирует текущий поток (неблокирующий)
  • 🚀 Запускает фоновую задачу "fire-and-forget" (запустил и забыл)
  • 🔄 Возвращает Job для управления жизненным циклом
  • 📤 Не возвращает результат вычислений
  • 💡 Идеально подходит для побочных эффектов, логирования, отправки событий

3. async - Асинхронные вычисления с результатом

async — это функция-строитель, которая запускает новую сопрограмму и возвращает Deferred<T> (отложенное значение), которое содержит будущий результат вычислений.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Запуск параллельных вычислений")
    
    // Запускаем две асинхронные задачи
    val deferred1 = async {
        delay(1000)
        println("Задача 1 завершена")
        42 // Возвращаемое значение
    }
    
    val deferred2 = async {
        delay(800)
        println("Задача 2 завершена")
        "Результат"
    }
    
    // Параллельное выполнение обеих задач
    println("Ожидаем результаты...")
    
    // Получаем результаты (приостанавливаем выполнение, но не блокируем поток)
    val result1 = deferred1.await()  // Приостановка до получения результата
    val result2 = deferred2.await()
    
    println("Результат 1: $result1, Результат 2: $result2")
    
    // Альтернативно: запуск с ленивой инициализацией
    val lazyDeferred = async(start = CoroutineStart.LAZY) {
        println("Ленивая задача выполняется")
        "Ленивый результат"
    }
    
    // Ленивая задача выполнится только при вызове await() или start()
    println("Ленивая задача еще не запущена")
    val lazyResult = lazyDeferred.await()
    println("Ленивый результат: $lazyResult")
}

Ключевые характеристики async:

  • Не блокирует текущий поток
  • 📊 Возвращает Deferred<T> — отложенное значение с результатом
  • 🎯 Предназначен для параллельных вычислений с возвращаемым значением
  • ⏱️ await() приостанавливает сопрограмму (не поток!) до получения результата
  • 🚀 Параллельное выполнение нескольких async-задач ускоряет общее время выполнения

Сравнительная таблица

ХарактеристикаrunBlockinglaunchasync
НазначениеБлокирующий запуск для тестов/mainЗапуск фоновой задачи без результатаПараллельные вычисления с результатом
ВозвращаетT (результат лямбды)JobDeferred<T>
БлокировкаДа, блокирует потокНетНет
РезультатПрямой доступ к результатуНет результатаЧерез await()
ИспользованиеТесты, main-функцииПобочные эффекты, логиПараллельные вычисления
ОтменаДа (через корутину)Да (через Job)Да (через Deferred)

Практический пример использования всех трех подходов

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

suspend fun fetchUserData(): String {
    delay(1000) // Имитация сетевого запроса
    return "Данные пользователя"
}

suspend fun fetchUserAvatar(): String {
    delay(800) // Имитация загрузки аватара
    return "Аватар пользователя"
}

fun main() = runBlocking { // 1. runBlocking для main функции
    println("=== Запуск приложения ===")
    
    // 2. launch для фоновой задачи логирования
    val logJob = launch {
        var counter = 0
        while (isActive) {
            delay(500)
            println("Фоновое логирование #${++counter}")
        }
    }
    
    // 3. async для параллельных вычислений
    val time = measureTimeMillis {
        val userDataDeferred = async { fetchUserData() }
        val avatarDeferred = async { fetchUserAvatar() }
        
        // Параллельное выполнение обоих запросов
        val userData = userDataDeferred.await()
        val avatar = avatarDeferred.await()
        
        println("Получены данные: $userData")
        println("Получен аватар: $avatar")
    }
    
    println("Общее время выполнения: ${time}ms") // ~1000ms вместо 1800ms
    
    // Отмена задачи логирования
    logJob.cancel()
    logJob.join()
    
    println("=== Приложение завершено ===")
}

Рекомендации по использованию

  1. На Android:

    • Используйте launch для операций без возвращаемого значения (навигация, показ Toast)
    • Используйте async для параллельных сетевых запросов или вычислений
    • Избегайте runBlocking в UI-потоке, так как это заблокирует интерфейс
  2. В Unit-тестах:

    • Используйте runBlocking для тестирования suspend-функций
    • Используйте runTest (из kotlinx-coroutines-test) для более продвинутого тестирования
  3. Для оптимизации производительности:

    • Используйте async с awaitAll() для параллельного выполнения независимых задач
    • Комбинируйте launch и async в structured concurrency
// Пример оптимизации с параллельными запросами
suspend fun loadDashboardData() = coroutineScope {
    val news = async { fetchNews() }
    val weather = async { fetchWeather() }
    val userProfile = async { fetchUserProfile() }
    
    // Все три запроса выполняются параллельно
    val results = awaitAll(news, weather, userProfile)
    DashboardData(results[0], results[1], results[2])
}

Заключение

Выбор между launch, async и runBlocking зависит от конкретной задачи:

  • runBlocking — для синхронного стиля в точках входа (main, тесты)
  • launch — для фоновых задач без возвращаемого значения
  • async — для параллельных вычислений с возвратом результата

Понимание этих различий критически важно для написания эффективного, неблокирующего асинхронного кода на Kotlin, особенно в контексте Android разработки, где отзывчивость UI является ключевым требованием.