Как синхронизировать выполнение корутин для последовательного выполнения
Что проверяют
Понимаете ли вы, что код внутри одной корутины и так выполняется последовательно, а проблема начинается, когда корутин несколько и они запущены конкурентно. Следом обычно спрашивают, чем Mutex отличается от synchronized и что делать с повторными нажатиями кнопки.
Как отвечать
Сначала база: вызовы suspend-функций подряд в одной корутине идут строго по очереди, второй начнётся после возврата первого. А вот два viewModelScope.launch подряд работают параллельно, и порядок их завершения не гарантирован. Инструменты для упорядочивания:
joinиawait: второй запуск ждётjob.join()илиdeferred.await()первого. Подходит, когда зависимость видна в одном месте кода;Mutex: изkotlinx.coroutines.sync,mutex.withLock { ... }приостанавливает корутину, а не блокирует поток, поэтому внутри можно вызыватьsuspend-функции. Вsynchronizedтак нельзя;Channelс одним потребителем: события кладутся в канал, одна корутина читает их черезfor (item in channel)и обрабатывает по одному. Надёжный способ выстроить очередь операций;- Flow-операторы:
flatMapConcatобрабатывает элементы последовательно,flatMapLatestиcollectLatestотменяют предыдущую работу, если важен только последний запрос, например для поиска по вводу; - отмена предыдущего:
job?.cancel()перед новымlaunch, когда старый результат уже не нужен.
Для Android уместно добавить, что состояние экрана удобно менять из одной точки через MutableStateFlow и его атомарный update { }, тогда гонки за UI-состояние не возникают.
Где спотыкаются
Используют runBlocking в Android-коде. На главном потоке это заморозка интерфейса и риск ANR, а для тестов есть runTest.
Надеются на limitedParallelism(1) или однопоточный диспетчер. Он гарантирует один поток, но на каждой точке приостановки начинает выполняться другая корутина, и критическая секция из нескольких suspend-вызовов не защищена.
Вызывают withLock повторно внутри уже захваченного Mutex. Он не реентерабельный, корутина ждёт сама себя и зависает навсегда.