Какие знаешь проблемы при нарушении Structured concurrency?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь проблемы при нарушении Structured concurrency?
Structured concurrency — это принцип, который требует, чтобы жизненный цикл корутин соответствовал структуре программы. Нарушение этого принципа приводит к серьёзным проблемам. Рассмотрю самые критичные из них.
1. Утечка памяти из-за зависших корутин
Проблема:
Если корутина создана, но её жизненный цикл не связан с областью видимости компонента, она может остаться в памяти после уничтожения компонента:
class BadViewModel : ViewModel() {
init {
// ПЛОХО: создаём корутину без структуры
GlobalScope.launch {
while (true) {
delay(1000)
println("Работаю")
}
}
}
}
// ViewModel удаляется, но корутина продолжает работать!
Корутина продолжит выполняться в памяти, даже если ViewModel давно уничтожена.
Решение:
class GoodViewModel : ViewModel() {
init {
viewModelScope.launch {
while (true) {
delay(1000)
println("Работаю")
}
}
// Корутина автоматически отменяется при уничтожении ViewModel
}
}
2. Потеря исключений
Проблема:
Без структурированного контекста исключения могут просто потеряться:
GlobalScope.launch {
throw Exception("Ошибка")
// Кто поймает эту ошибку? Никто!
}
println("Продолжаю работу")
// Программа не упадёт, но ошибка затеряется
Это затрудняет отладку и скрывает баги.
Решение:
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Поймали: ${exception.message}")
}
viewModelScope.launch(exceptionHandler) {
throw Exception("Ошибка")
// Будет перехвачена обработчиком
}
3. Race conditions и состояние гонки
Проблема:
Без правильной синхронизации нескольких корутин возникают гонки данных:
var counter = 0
repeat(1000) {
GlobalScope.launch {
counter++ // data race!
}
}
delay(2000)
println(counter) // Случайное число < 1000
Данные повреждаются, так как нет гарантии безопасности при параллельном доступе.
Решение:
val mutex = Mutex()
var counter = 0
repeat(1000) {
viewModelScope.launch {
mutex.withLock {
counter++ // Безопасно
}
}
}
4. Неконтролируемое создание потоков
Проблема:
Без структуры легко случайно создать тысячи потоков:
repeat(10000) {
thread {
// Плохо: создаём 10000 потоков!
Thread.sleep(10000)
}
}
// OutOfMemoryError!
Это быстро приводит к исчерпанию ресурсов.
Решение:
repeat(10000) {
viewModelScope.launch(Dispatchers.Default) {
delay(10000)
// Корутины используют пул потоков, не создавая новые потоки
}
}
5. Отмена не распространяется
Проблема:
Если нет иерархии Job, отмена не влияет на дочерние корутины:
val parentJob = Job()
val scope = CoroutineScope(parentJob)
scope.launch {
launch {
println("Дочерняя 1")
}
launch {
println("Дочерняя 2")
}
}
parentJob.cancel() // Отменяются обе дочерние корутины
Но если использовать GlobalScope:
GlobalScope.launch {
GlobalScope.launch { println("Зависла") } // Не может быть отменена
}
6. Неправильный жизненный цикл UI
Проблема:
Если корутина не привязана к жизненному циклу Activity/Fragment, она может обновлять UI после уничтожения:
class BadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch {
val data = fetchData()
// Activity может быть уничтожена, а мы всё ещё обновляем UI!
textView.text = data
}
}
}
Это приводит к краху приложения.
Решение:
class GoodActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { data ->
textView.text = data
}
}
}
}
}
7. Deadlocks и ожидание
Проблема:
Без структуры легко создать deadlock:
val job = GlobalScope.launch {
delay(1000)
}
runBlocking { // Блокирует главный поток!
job.join()
}
// Если это главный поток Android, приложение зависает
Сравнение подходов
| Проблема | GlobalScope | viewModelScope | lifecycleScope |
|---|---|---|---|
| Утечка памяти | Да | Нет | Нет |
| Потеря исключений | Да | Можно | Можно |
| Race conditions | Да | Возможны | Возможны |
| Отмена при уничтожении | Нет | Да | Да |
| Жизненный цикл UI | Нет | Хорошо | Идеально |
Заключение
Structured concurrency обеспечивает безопасность и предсказуемость асинхронного кода. Нарушение этого принципа приводит к утечкам памяти, потере ошибок, race conditions и сложным в отладке багам. Всегда привязывай корутины к структуре программы через viewModelScope, lifecycleScope или другие структурированные контексты.