Как в suspend функции запустить выполнение задач параллельно
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Параллельное выполнение в suspend-функциях Kotlin
В Kotlin корутинах параллельное выполнение задач в suspend-функциях достигается несколькими способами. Основные подходы включают использование async/await, coroutineScope, и launch в определенных контекстах.
Основной подход: async/await с coroutineScope
Наиболее распространенный способ — использование async корутин-билдера внутри coroutineScope или supervisorScope:
suspend fun fetchDataParallel(): Pair<String, Int> = coroutineScope {
val deferredString = async { fetchStringData() }
val deferredInt = async { fetchIntData() }
val stringResult = deferredString.await()
val intResult = deferredInt.await()
Pair(stringResult, intResult)
}
suspend fun fetchStringData(): String {
delay(1000) // Имитация долгой операции
return "Результат строки"
}
suspend fun fetchIntData(): Int {
delay(1500) // Имитация другой долгой операции
return 42
}
Ключевые моменты:
asyncзапускает корутину, которая возвращаетDeferred<T>(отложенный результат)await()— suspend-функция, которая ожидает результатcoroutineScopeсоздает область видимости, где все дочерние корутины должны завершиться
Структурный параллелизм с обработкой ошибок
Для обработки ошибок в параллельных задачах используйте supervisorScope:
suspend fun processMultipleSources(): Result = supervisorScope {
val userDeferred = async { fetchUserData() }
val settingsDeferred = async { fetchUserSettings() }
try {
val user = userDeferred.await()
val settings = settingsDeferred.await()
Result.Success(user, settings)
} catch (e: Exception) {
Result.Error(e)
}
}
Параллелизм с launch для fire-and-forget задач
Если не нужны результаты сразу, можно использовать launch:
suspend fun startBackgroundTasks() = coroutineScope {
// Запускаем несколько фоновых задач
launch { uploadLogs() }
launch { refreshCache() }
launch { sendAnalytics() }
// Все задачи будут выполняться параллельно
}
Оптимизация с помощью withContext для разных диспетчеров
Можно комбинировать задачи с разными диспетчерами:
suspend fun complexParallelOperation(): CombinedResult = coroutineScope {
val ioTask = async(Dispatchers.IO) { fetchFromNetwork() }
val cpuTask = async(Dispatchers.Default) { processData() }
val uiTask = async(Dispatchers.Main) { prepareUIComponents() }
CombinedResult(
networkData = ioTask.await(),
processedData = cpuTask.await(),
uiComponents = uiTask.await()
)
}
Параллельная обработка коллекций
Для параллельной обработки коллекций:
suspend fun processItemsParallel(items: List<Item>): List<Result> = coroutineScope {
items.map { item ->
async { processItem(item) }
}.awaitAll()
}
// Или с ограничением параллелизма
suspend fun processWithLimit(items: List<Item>, limit: Int): List<Result> = coroutineScope {
val chunks = items.chunked(limit)
chunks.flatMap { chunk ->
chunk.map { item ->
async { processItem(item) }
}.awaitAll()
}
}
Важные особенности и лучшие практики
- Структурный параллелизм — все запущенные корутины должны завершиться внутри области видимости
- Отмена — отмена родительской корутины отменяет все дочерние
- Обработка исключений — в
coroutineScopeпервая ошибка отменяет все остальные задачи - Диспетчеры — выбирайте соответствующий диспетчер для типа операции:
Dispatchers.IO— для операций ввода/выводаDispatchers.Default— для CPU-интенсивных задачDispatchers.Main— для работы с UI (в Android)
Пример из реальной практики Android
class UserRepository {
suspend fun loadUserProfile(userId: String): UserProfile = coroutineScope {
val userDeferred = async(Dispatchers.IO) { api.getUser(userId) }
val postsDeferred = async(Dispatchers.IO) { api.getUserPosts(userId) }
val friendsDeferred = async(Dispatchers.IO) { api.getUserFriends(userId) }
val user = userDeferred.await()
val posts = postsDeferred.await()
val friends = friendsDeferred.await()
UserProfile(user, posts, friends)
}
}
Этот подход позволяет сократить время выполнения с последовательного T1 + T2 + T3 до максимального из max(T1, T2, T3), что особенно важно для мобильных приложений, где отзывчивость критически важна.
Помните, что чрезмерный параллелизм может привести к contention и проблемам с памятью, поэтому всегда анализируйте необходимость параллельного выполнения и используйте appropriate диспетчеры для разных типов операций.