Как запустить suspend функцию из синхронного контекста
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Запуск 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-системами.