Как запустить асинхронный код из обычной функции с Coroutines
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Запуск асинхронного кода из обычной функции с 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")
}
}
Ключевые рекомендации:
- Всегда управляйте жизненным циклом корутин — отменяйте их, когда они больше не нужны
- Используйте правильные диспетчеры:
Dispatchers.Main— работа с UIDispatchers.IO— сетевые запросы, работа с БДDispatchers.Default— тяжелые вычисления
- Обрабатывайте исключения — используйте
try-catchилиCoroutineExceptionHandler - Избегайте
GlobalScopeв production коде Android - Для 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.