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

Как в suspend функции запустить выполнение задач параллельно

2.2 Middle🔥 121 комментариев
#Kotlin основы#Многопоточность и асинхронность

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

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

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

Параллельное выполнение в 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()
    }
}

Важные особенности и лучшие практики

  1. Структурный параллелизм — все запущенные корутины должны завершиться внутри области видимости
  2. Отмена — отмена родительской корутины отменяет все дочерние
  3. Обработка исключений — в coroutineScope первая ошибка отменяет все остальные задачи
  4. Диспетчеры — выбирайте соответствующий диспетчер для типа операции:
    • 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 диспетчеры для разных типов операций.