Что такое withContext в Coroutines?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
withContext в Kotlin Coroutines
Определение
withContext — это suspend-функция, которая переключает контекст выполнения корутины без создания новой. Она позволяет выполнить блок кода в другом CoroutineContext (например, на другом потоке) и затем вернуться в исходный контекст, ожидая завершения блока.
Основная сигнатура
suspend inline fun <T> withContext(
context: CoroutineContext,
noinline block: suspend () -> T
): T
Функция suspend, поэтому может быть вызвана только из корутины или другой suspend-функции.
Главное отличие от launch и async
- launch и async: создают новую корутину
- withContext: переключает контекст текущей корутины, не создавая новую
Это критическое различие: withContext блокирует текущий код, пока блок внутри не завершится, и гарантирует, что мы вернёмся в исходный контекст.
Реальный пример из моего опыта
Разрабатывал приложение с базой данных SQLite. Нужно было выполнять длительные операции БД без блокирования главного потока:
class UserRepository (
private val userDao: UserDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun loadUserData(userId: Long): User = withContext(ioDispatcher) {
// Этот код выполняется на IO-потоке
val user = userDao.getUser(userId)
val posts = userDao.getUserPosts(userId)
val comments = userDao.getUserComments(userId)
user.apply {
this.posts = posts
this.comments = comments
}
}
// Результат вернётся в контекст вызывающей корутины
}
// В ViewModel
viewModelScope.launch {
// Выполняется на Main
val user = userRepository.loadUserData(123) // suspend call
// Когда вернётся из withContext, опять на Main
updateUI(user)
}
Практический паттерн: DB + Network в одном методе
suspend fun syncUserProfile(userId: Long): Result<User> = withContext(Dispatchers.IO) {
try {
// Получаем с сервера
val serverUser = api.getUser(userId)
// Сохраняем в БД
database.userDao.insertUser(serverUser)
// Загружаем комплексные данные
val localUser = database.userDao.getUserWithRelations(userId)
Result.success(localUser)
} catch (e: Exception) {
Result.failure(e)
}
}
// Вызов из UI (Main dispatcher)
launchUI {
val result = syncUserProfile(userId)
when (result) {
is Success -> renderUser(result.data)
is Failure -> showError(result.error)
}
}
Конкретные Dispatchers
// 1. Dispatchers.IO — для файлов, БД, сети
withContext(Dispatchers.IO) {
val content = File("file.txt").readText()
}
// 2. Dispatchers.Default — для CPU-intensive операций
withContext(Dispatchers.Default) {
val result = expensiveCalculation()
}
// 3. Dispatchers.Main — для обновления UI (редко нужен)
withContext(Dispatchers.Main) {
textView.text = "Updated"
}
// 4. Специальный контекст
withContext(Dispatchers.IO + coroutineExceptionHandler) {
// С обработкой ошибок
}
Ошибки, которые я видел
// Ошибка 1: Вложенный withContext в один dispatcher
withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { // избыточно
userDao.getUser()
}
}
// Ошибка 2: Забыли return значение
suspend fun getData(): String = withContext(Dispatchers.IO) {
// должен быть результат!
}
// Правильно:
suspend fun getData(): String = withContext(Dispatchers.IO) {
database.getData() // возвращает String
}
Когда использовать withContext
- ✅ Переключение потоков внутри suspend-функции
- ✅ Выполнение блокирующих операций на IO
- ✅ CPU-intensive вычисления на Default
- ✅ Структурированная отмена корутин
- ❌ Для параллельного выполнения (use launch/async instead)
withContext — один из столпов правильной работы с Coroutines, без которого нельзя написать отзывчивое приложение.