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

Что такое coroutine scope и coroutine context? В чём разница между launch и async?

3.0 Senior🔥 231 комментариев
#Kotlin основы#Многопоточность и асинхронность

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

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

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

Корутины в Kotlin: Scope, Context, Launch и Async

Coroutine Scope (Область видимости корутин)

Coroutine Scope — это объект, который определяет жизненный цикл корутин и предоставляет возможность их запуска. Каждая корутина должна запускаться в определенной области видимости, которая отвечает за:

  • Отслеживание всех дочерних корутин
  • Автоматическую отмену всех дочерних корутин при отмене scope
  • Предоставление контекста выполнения по умолчанию
// Пример создания собственного scope
class MyViewModel : ViewModel() {
    private val viewModelScope = CoroutineScope(
        SupervisorJob() + Dispatchers.Main + CoroutineName("MyViewModelScope")
    )
    
    fun loadData() {
        viewModelScope.launch {
            // Корутина будет отменена при очистке ViewModel
            fetchData()
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel() // Отменяем все корутины
    }
}

Coroutine Context (Контекст корутин)

Coroutine Context — это набор элементов, который определяет поведение и окружение корутины. Контекст является неизменяемым (immutable) и может содержать:

  1. Диспетчер (Dispatcher) — определяет поток выполнения
  2. Job — управляет жизненным циклом корутины
  3. Имя корутины (CoroutineName) — для отладки
  4. Обработчики исключений (CoroutineExceptionHandler)
// Пример работы с контекстом
suspend fun exampleContext() {
    val context = Dispatchers.IO + CoroutineName("NetworkCall") + SupervisorJob()
    
    withContext(context) {
        // Код выполняется в IO dispatcher с именем "NetworkCall"
        println("Running in ${coroutineContext[CoroutineName]}")
    }
    
    // Комбинирование контекстов
    val newContext = coroutineContext + Dispatchers.Default
}

Разница между Launch и Async

Launch — "Запустить и забыть"

Launch используется для запуска корутин, которые не возвращают результат. Это аналог fire-and-forget операций:

fun launchExample() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        // Выполняем фоновую работу
        delay(1000)
        println("Operation completed")
        // Нет возвращаемого значения
    }
    
    // Можем управлять Job, но не можем получить результат
    job.cancel() // Можем отменить
}

Характеристики launch:

  • Возвращает Job для управления жизненным циклом
  • Не возвращает результат вычислений
  • Исключения в launch приводят к отмене родительской корутины (если не используется SupervisorJob)
  • Используется для side-effects операций

Async — "Асинхронное вычисление с результатом"

Async используется для запуска корутин, которые возвращают результат. Возвращает Deferred<T> — отложенное значение:

suspend fun asyncExample() {
    val deferredResult = CoroutineScope(Dispatchers.Default).async {
        // Выполняем вычисление
        delay(500)
        42 // Возвращаем результат
    }
    
    // Получаем результат с помощью await()
    val result = deferredResult.await()
    println("Result: $result") // Result: 42
    
    // Можно запускать несколько async параллельно
    val deferred1 = async { fetchDataFromSource1() }
    val deferred2 = async { fetchDataFromSource2() }
    
    val results = awaitAll(deferred1, deferred2)
}

Характеристики async:

  • Возвращает Deferred<T> (аналог Future/Promise)
  • Требует вызова await() для получения результата
  • Исключения в async не выбрасываются сразу, а откладываются до вызова await()
  • Позволяет выполнять параллельные вычисления

Ключевые отличия

АспектLaunchAsync
Возвращаемое значениеJobDeferred<T>
РезультатНет возвращаемого значенияВозвращает результат через await()
Обработка исключенийИсключения выбрасываются сразуИсключения откладываются до await()
ИспользованиеДля фоновых операций без результатаДля параллельных вычислений с результатом
ПараллелизмНе предназначен для параллельных результатовОптимален для параллельных операций

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

class DataLoader {
    suspend fun loadUserData(userId: String): UserData {
        // Параллельная загрузка данных
        val profileDeferred = async { api.getUserProfile(userId) }
        val postsDeferred = async { api.getUserPosts(userId) }
        val friendsDeferred = async { api.getUserFriends(userId) }
        
        // Ожидаем все результаты параллельно
        val (profile, posts, friends) = awaitAll(
            profileDeferred, 
            postsDeferred, 
            friendsDeferred
        )
        
        return UserData(profile, posts, friends)
    }
    
    fun trackUserActivity(userId: String) {
        // Запускаем фоновое отслеживание
        CoroutineScope(Dispatchers.IO).launch {
            analytics.trackUserSession(userId)
            // Нет необходимости возвращать результат
        }
    }
}

Важные принципы работы

  1. Structured Concurrency: Корутины следуют принципу структурированного параллелизма, где дочерние корутины завершаются перед родительскими
  2. Отмена распространяется: При отмене родительской корутины отменяются все дочерние
  3. Контекст наследуется: Дочерние корутины наследуют контекст от родительских, но могут его переопределять
  4. SupervisorJob: Позволяет обрабатывать исключения в дочерних корутинах независимо

Понимание различий между scope, context, launch и async критически важно для написания корректных, эффективных и безопасных асинхронных приложений на Kotlin.

Что такое coroutine scope и coroutine context? В чём разница между launch и async? | PrepBro