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

Как запустить suspend функцию из синхронного контекста

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

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

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

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

Запуск suspend-функции из синхронного контекста

В Kotlin coroutines являются основным механизмом для асинхронного программирования, но иногда возникает необходимость запустить suspend-функцию из обычного синхронного кода (например, из main(), тестов, callback-based API или устаревшего Java-кода). Поскольку suspend-функции могут выполняться только внутри корутины, требуется создать специальный контекст.

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

1. Использование runBlocking

Наиболее прямой способ — блокировать текущий поток до завершения корутины. Это подходит для тестов, утилит или точек входа в приложение.

import kotlinx.coroutines.*

fun main() {
    runBlocking { // Создаёт новый event-loop и блокирует текущий поток
        val result = someSuspendFunction()
        println("Result: $result")
    }
}

suspend fun someSuspendFunction(): String {
    delay(1000)
    return "Hello from suspend"
}

Важно: runBlocking не следует использовать в Android UI-потоке, так как это приведёт к блокировке интерфейса. В Android используйте lifecycleScope или viewModelScope.

2. Использование CoroutineScope с блокировкой результата

Для получения результата из suspend-функции можно использовать async с последующим await():

fun getResultSynchronously(): String = runBlocking {
    async { someSuspendFunction() }.await()
}

3. Использование suspendCoroutine или callbackFlow для интеграции с callback-based API

Если нужно адаптировать callback-ориентированный API к suspend-функциям:

suspend fun awaitCallback(): String = suspendCoroutine { continuation ->
    someLegacyApi.registerCallback { result ->
        continuation.resume(result)
    }
}

Продвинутые сценарии

4. Использование CompletableDeferred для ручного управления

Когда требуется более тонкий контроль над выполнением:

import kotlinx.coroutines.*

fun launchSuspendFromSync(): Deferred<String> {
    val deferred = CompletableDeferred<String>()
    
    GlobalScope.launch {
        try {
            val result = someSuspendFunction()
            deferred.complete(result)
        } catch (e: Exception) {
            deferred.completeExceptionally(e)
        }
    }
    
    return deferred
}

// Использование
fun main() {
    runBlocking {
        val result = launchSuspendFromSync().await()
        println(result)
    }
}

5. Интеграция с реактивными потоками (Flow)

Если suspend-функция возвращает Flow, можно использовать first() или toList():

fun getFirstValue(): String = runBlocking {
    someFlowFunction().first()
}

suspend fun someFlowFunction(): Flow<String> {
    return flow {
        emit("Value")
    }
}

Ключевые рекомендации и предостережения

  • Избегайте runBlocking в production-коде Android, особенно в UI-потоке. Вместо этого используйте scoped coroutines (lifecycleScope, viewModelScope).
  • Для тестирования suspend-функций используйте runTest из библиотеки kotlinx-coroutines-test.
  • При интеграции с legacy-кодом рассмотрите возможность рефакторинга для нативной поддержки корутин.
  • Не забывайте обрабатывать исключения — используйте try-catch блоки или CoroutineExceptionHandler.
  • Для сложных сценариев миграции используйте интероперабельные обёртки, постепенно переходя от синхронного к асинхронному коду.

Пример для Android

// НЕПРАВИЛЬНО в Android Activity
fun onCreate() {
    runBlocking { // Блокирует UI-поток!
        loadData()
    }
}

// ПРАВИЛЬНО в Android Activity
fun onCreate() {
    lifecycleScope.launch {
        loadData() // Suspend функция выполняется без блокировки UI
    }
}

Выбор метода зависит от контекста: runBlocking для утилит и тестов, scoped coroutines для Android, и специальные адаптеры для интеграции с legacy-системами.

Как запустить suspend функцию из синхронного контекста | PrepBro