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

Как запустить асинхронный код из обычной функции с Coroutines

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

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

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

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

Запуск асинхронного кода из обычной функции с Coroutines

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

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

1. Использование runBlocking (для тестов и консольных приложений)

fun main() {
    runBlocking {
        val result = async { fetchData() }.await()
        println("Результат: $result")
    }
}

suspend fun fetchData(): String {
    delay(1000)
    return "Данные получены"
}

Важно: runBlocking блокирует текущий поток до завершения всех корутин внутри блока. Не используйте в production коде Android, кроме как в main() или тестах.

2. Использование CoroutineScope с явным жизненным циклом

class MyViewModel : ViewModel() {
    private val viewModelScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    
    fun loadData() {
        viewModelScope.launch {
            val data = withContext(Dispatchers.IO) {
                repository.fetchData()
            }
            updateUI(data)
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel() // Очистка ресурсов
    }
}

3. Использование GlobalScope (с осторожностью!)

fun startBackgroundTask() {
    GlobalScope.launch(Dispatchers.IO) {
        val data = performNetworkRequest()
        withContext(Dispatchers.Main) {
            updateUI(data)
        }
    }
}

Предупреждение: GlobalScope создает корутины с жизненным циклом всего приложения. Это может привести к утечкам памяти, если корутина не завершится корректно.

Рекомендуемый подход для Android

4. Использование lifecycleScope или viewModelScope

class MyActivity : AppCompatActivity() {
    
    fun loadUserData() {
        // lifecycleScope автоматически отменяется при уничтожении Activity
        lifecycleScope.launch {
            try {
                val user = withContext(Dispatchers.IO) {
                    apiService.getUser(userId)
                }
                displayUser(user)
            } catch (e: Exception) {
                showError(e)
            }
        }
    }
    
    // Или с обработкой состояний
    fun loadDataWithState() {
        lifecycleScope.launch {
            when (val result = loadDataSafely()) {
                is Result.Success -> showData(result.data)
                is Result.Error -> showError(result.exception)
            }
        }
    }
    
    private suspend fun loadDataSafely(): Result<Data> = withContext(Dispatchers.IO) {
        try {
            Result.Success(repository.fetchData())
        } catch (e: Exception) {
            Result.Error(e)
        }
    }
}

5. Создание кастомного CoroutineScope

class DataProcessor {
    private val processorScope = CoroutineScope(
        Dispatchers.Default + CoroutineName("DataProcessor") + SupervisorJob()
    )
    
    fun processAsync(data: List<Int>, callback: (Result) -> Unit) {
        processorScope.launch {
            val result = processData(data)
            withContext(Dispatchers.Main) {
                callback(result)
            }
        }
    }
    
    private suspend fun processData(data: List<Int>): Result {
        return withContext(Dispatchers.Default) {
            // Тяжелые вычисления
            Result.Success(data.sum())
        }
    }
    
    fun cleanup() {
        processorScope.cancel() // Вызывать при завершении работы
    }
}

6. Использование async для параллельных операций

fun fetchMultipleSources(): Deferred<CombinedResult> {
    return CoroutineScope(Dispatchers.IO).async {
        val deferred1 = async { api.getSource1() }
        val deferred2 = async { api.getSource2() }
        val deferred3 = async { api.getSource3() }
        
        CombinedResult(
            source1 = deferred1.await(),
            source2 = deferred2.await(),
            source3 = deferred3.await()
        )
    }
}

// Использование
fun main() {
    runBlocking {
        val result = fetchMultipleSources().await()
        println("Все данные получены: $result")
    }
}

Ключевые рекомендации:

  1. Всегда управляйте жизненным циклом корутин — отменяйте их, когда они больше не нужны
  2. Используйте правильные диспетчеры:
    • Dispatchers.Main — работа с UI
    • Dispatchers.IO — сетевые запросы, работа с БД
    • Dispatchers.Default — тяжелые вычисления
  3. Обрабатывайте исключения — используйте try-catch или CoroutineExceptionHandler
  4. Избегайте GlobalScope в production коде Android
  5. Для Android используйте lifecycleScope/viewModelScope — они уже интегрированы с жизненным циклом компонентов

Пример обработки ошибок:

fun safeAsyncOperation() {
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Ошибка в корутине: ${exception.message}")
    }
    
    CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
        val data = performRiskyOperation()
        // Обработка результата
    }
}

Выбор конкретного подхода зависит от контекста: в Android приложениях предпочтительнее использовать viewModelScope или lifecycleScope, в библиотеках — создавать кастомные scope с явным управлением жизненным циклом, а в тестах — runBlocking или TestScope.

Как запустить асинхронный код из обычной функции с Coroutines | PrepBro