Что такое coroutine scope и coroutine context? В чём разница между launch и async?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Корутины в 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) и может содержать:
- Диспетчер (Dispatcher) — определяет поток выполнения
- Job — управляет жизненным циклом корутины
- Имя корутины (CoroutineName) — для отладки
- Обработчики исключений (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() - Позволяет выполнять параллельные вычисления
Ключевые отличия
| Аспект | Launch | Async |
|---|---|---|
| Возвращаемое значение | Job | Deferred<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)
// Нет необходимости возвращать результат
}
}
}
Важные принципы работы
- Structured Concurrency: Корутины следуют принципу структурированного параллелизма, где дочерние корутины завершаются перед родительскими
- Отмена распространяется: При отмене родительской корутины отменяются все дочерние
- Контекст наследуется: Дочерние корутины наследуют контекст от родительских, но могут его переопределять
- SupervisorJob: Позволяет обрабатывать исключения в дочерних корутинах независимо
Понимание различий между scope, context, launch и async критически важно для написания корректных, эффективных и безопасных асинхронных приложений на Kotlin.