Контракт между Job-ами — ключевая фича корутин, которая спасает от утечек и делает код предсказуемым. И тем самым реализует Structured Concurrency.
Основной контракт (золотое правило)
Job-а родительской корутины ждёт завершения всех своих дочерних корутин и дочерняя корутина не может пережить родителя.
Что происходит автоматически (контракт)
| Ситуация | Что произойдёт автоматически |
|---|---|
| Родитель завершился успешно | Ждёт всех детей → только потом завершается сам |
| Родитель отменён (cancel) | Все дети отменяются сразу (рекурсивно) |
| Один из детей упал с исключением | По умолчанию → родитель отменяется → все братья/сестра отменяются |
| Хотим, чтобы падение одного не убивало остальных | Используем SupervisorJob или supervisorScope |
Важные детали, которые любят спрашивать
| Вопрос | Правильный ответ |
|---|---|
launch внутри launch — кто кого ждёт? | Внешний launch ждёт внутренний |
| Можно ли отменить только одного ребёнка? | Да, через childJob.cancel() — остальные продолжат |
Что делает coroutineScope { }? | Создаёт под-скоп, ждёт всех детей, пробрасывает первое исключение |
Что делает supervisorScope { }? | То же, но не отменяет братьев при падении одного |
async тоже ребёнок? | Да, полностью подчиняется тем же правилам |
GlobalScope.launch — нарушает контракт? | Да! У него нет родителя => утечка и непредсказуемость |
Резюмирующий ответ
В корутинах действует строгая иерархия Job-ов: родитель всегда ждёт всех дочерних корутин и отменяет их при своей отмене. По умолчанию падение одного дочерней отменяет родителя и всех братьев/сестёр — это обычный Job. Если нужно изолировать ошибки — используем SupervisorJob или supervisorScope: тогда падение одного дочерней не трогает остальных. Это и есть Structured Concurrency — гарантия отсутствия утечек и предсказуемое поведение.