Что такое join в синхронизации потоков
Что проверяют
Понимаете ли вы, что даёт ожидание завершения потока кроме самой паузы, и знаете ли цену такого ожидания на мобильном клиенте. Разговор почти всегда продолжается корутинами и вопросом о том, чем в этот момент занят главный поток.
Как отвечать
join блокирует вызывающий поток, пока целевой не завершится. Есть вариант с таймаутом, и он прерываем: при прерывании бросается исключение, а сам признак прерывания сбрасывается. Важная часть, которую редко называют, это гарантия видимости: всё, что успел записать завершившийся поток, видно вызывающему сразу после возврата, без дополнительной синхронизации.
Для Android ответ надо продолжить. Блокировать главный поток нельзя: ожидание на нём это замерший интерфейс и в худшем случае системный диалог о неотвечающем приложении. Поэтому вместо ручных потоков берут корутины, где ожидание не занимает поток, а приостанавливает выполнение:
val job = scope.launch { warmUpCache() }
job.join()Здесь же уместна структурная конкурентность: область сама дожидается дочерних задач, поэтому явное ожидание чаще нужно для порядка действий, чем для самого факта завершения. И разница с получением результата: ожидание завершается при любом исходе, включая отмену, а исключение наружу не выносит.
Где спотыкаются
Ждут завершения на главном потоке. Работа действительно становится последовательной и предсказуемой, только интерфейс при этом стоит, а система предлагает пользователю закрыть приложение.
Считают, что ожидание решает гонки. Оно упорядочивает лишь момент завершения: пока потоки работают параллельно, общие данные всё так же нужно защищать, и здесь не заменить ни блокировку, ни атомарные типы.
Глушат прерывание пустым блоком. Признак уже сброшен, восстановить его забывают, и отмена дальше по цепочке не срабатывает. Похожая ошибка в корутинах это дождаться завершения и не посмотреть на результат: сбой дочерней задачи наружу не выйдет, и авария выглядит как пустой экран без единой записи в логе.