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

Как дождаться результата выполнения двух параллельных задач

2.0 Middle🔥 222 комментариев
#Многопоточность и асинхронность

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

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

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

Ожидание результатов параллельных задач в Android/Kotlin

В Android-разработке на Kotlin существует несколько основных подходов для ожидания результатов выполнения двух или более параллельных задач. Выбор конкретного метода зависит от контекста выполнения (UI-поток или фоновый поток) и используемых механизмов асинхронности.

Основные подходы

1. Использование async/await в Kotlin Coroutines

Наиболее современный и рекомендуемый способ для фоновых операций:

import kotlinx.coroutines.*

suspend fun fetchParallelData(): Pair<Result1, Result2> = coroutineScope {
    val deferred1 = async { fetchDataFromSource1() }
    val deferred2 = async { fetchDataFromSource2() }
    
    // Ожидаем завершения обеих задач
    val result1 = deferred1.await()
    val result2 = deferred2.await()
    
    return@coroutineScope Pair(result1, result2)
}

// Использование
viewModelScope.launch {
    try {
        val (data1, data2) = fetchParallelData()
        // Обработка результатов
    } catch (e: Exception) {
        // Обработка ошибок
    }
}

Ключевые особенности:

  • Обе задачи запускаются параллельно
  • await() приостанавливает корутину, не блокируя поток
  • Автоматическая отмена при отмене родительской корутины

2. Комбинация async с таймаутом

suspend fun fetchWithTimeout(): Pair<Result1?, Result2?> = withTimeoutOrNull(5000) {
    coroutineScope {
        val deferred1 = async { fetchData1() }
        val deferred2 = async { fetchData2() }
        
        try {
            val result1 = deferred1.await()
            val result2 = deferred2.await()
            Pair(result1, result2)
        } catch (e: CancellationException) {
            null // Таймаут или отмена
        }
    }
}

3. Использование awaitAll() для нескольких задач

suspend fun fetchMultipleSources(): List<Any> = coroutineScope {
    val deferredTasks = listOf(
        async { fetchFromAPI1() },
        async { fetchFromAPI2() },
        async { fetchFromDatabase() }
    )
    
    deferredTasks.awaitAll() // Ждем все результаты
}

Альтернативные подходы

4. RxJava - оператор zip()

Single.zip(
    apiService.getData1().subscribeOn(Schedulers.io()),
    apiService.getData2().subscribeOn(Schedulers.io()),
    BiFunction { result1, result2 -> 
        Pair(result1, result2) 
    }
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ pair ->
    // Обработка результатов
}, { error ->
    // Обработка ошибки
})

5. Kotlin Flow - combine() или zip()

val flow1 = flow { emit(fetchData1()) }
val flow2 = flow { emit(fetchData2()) }

flow1.zip(flow2) { data1, data2 ->
    Pair(data1, data2)
}.flowOn(Dispatchers.IO)
.collect { pair ->
    // Получение объединенного результата
}

6. Колбэки с счетчиком (legacy-подход)

fun fetchBoth(callback: (Result1?, Result2?) -> Unit) {
    var result1: Result1? = null
    var result2: Result2? = null
    var completed = 0
    
    fun checkCompletion() {
        if (++completed == 2) {
            callback(result1, result2)
        }
    }
    
    fetchAsync1 { data ->
        result1 = data
        checkCompletion()
    }
    
    fetchAsync2 { data ->
        result2 = data
        checkCompletion()
    }
}

Рекомендации по выбору подхода

  1. Для новых проектов используйте Kotlin Coroutines с async/await
  2. Для UI-взаимодействий применяйте viewModelScope или lifecycleScope
  3. При работе с существующим RxJava-кодом используйте операторы комбинирования
  4. Для обработки таймаутов применяйте withTimeout или withTimeoutOrNull
  5. Для обработки ошибок каждой задачи независимо используйте supervisorScope

Пример с обработкой ошибок

suspend fun fetchSafely(): Pair<Result1?, Result2?> = supervisorScope {
    val deferred1 = async { 
        try { fetchData1() } catch (e: Exception) { null }
    }
    val deferred2 = async { 
        try { fetchData2() } catch (e: Exception) { null }
    }
    
    val result1 = deferred1.await()
    val result2 = deferred2.await()
    
    Pair(result1, result2)
}

Важные аспекты:

  • При использовании coroutineScope ошибка в любой задаче отменяет все остальные
  • supervisorScope позволяет обрабатывать ошибки независимо
  • Всегда учитывайте контекст выполнения (UI или фоновый поток)
  • Для CPU-интенсивных задач используйте Dispatchers.Default
  • Для IO-операций применяйте Dispatchers.IO

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