Запуск асинхронного кода Kotlin с диспетчерами

Я пытаюсь понять выполнение асинхронного кода с помощью сопрограмм Kotlin.

Почему я должен указывать диспетчер при запуске асинхронных блоков кода?

Следующий блок кода

fun doInParallel(): Unit = runBlocking {
    coroutineScope {
        launch { println("start A").also { Thread.sleep(1_000) }.also { println("finish A") } }
        launch { println("start B").also { Thread.sleep(1_000) }.also { println("finish B") } }
    }
}

всегда будет печатать

start A
finish A
start B
finish B

чего я не ожидал.

Только после явного указания диспетчера блоки асинхронного кода запускаются параллельно:

fun doInParallel(): Unit = runBlocking {
    coroutineScope {
        launch(Dispatchers.Default) { println("start A").also { Thread.sleep(1_000) }.also { println("finish A") } }
        launch(Dispatchers.Default) { println("start B").also { Thread.sleep(1_000) }.also { println("finish B") } }
    }
}

будет печатать

start A
start B
finish A
finish B

Почему я должен включать Dispatcher.Default?

Я использую org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-Beta (если это имеет какое-либо значение)

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Корутины должны приостанавливать, а не блокировать

Вместо Thread.sleep(...) используйте delay(...).

Thread.sleep блокирует поток, а с сопрограммами идея состоит в том, что вы хотите приостановить поток, а не заблокировать его. Когда поток приостанавливается от одной сопрограммы с помощью функции приостановки, такой как delay, этот же поток может свободно обслуживать другую сопрограмму — в данном случае ту, которая была запущена второй launch.

Причина, по которой диспетчер во втором примере имеет другое поведение, заключается в том, что coroutineScope наследует контекст сопрограммы от родительской области, которой в данном случае является runBlocking. Диспетчер по умолчанию в конструкторе сопрограмм runBlocking задокументирован:

CoroutineDispatcher по умолчанию для этого компоновщика является внутренней реализацией цикла событий, который обрабатывает продолжения в этом заблокированном потоке до завершения этой сопрограммы.

Другими словами, в вашем примере все выполняется внутри одного потока: основного потока (или любого другого потока, вызывающего doInParallel). Это в сочетании с использованием блокировки Thread.sleep означает, что код выполняется блокирующим образом и синхронно.

Обобщить:

  1. Измените свой код, чтобы напечатать имя потока, и вы увидите, что все выполняется в потоке main (если вы не укажете диспетчер).

  2. Измените использование Thread.sleep на delay, чтобы исправить основную проблему и разрешить одному потоку запускать обе запущенные сопрограммы.

fun doInParallel(): Unit = runBlocking {
  coroutineScope {
    launch { println("${Thread.currentThread().name} start A").also { delay(1_000) }.also { println("${Thread.currentThread().name} finish A") } }
    launch { println("${Thread.currentThread().name} start B").also { delay(1_000) }.also { println("${Thread.currentThread().name} finish B") } }
  }
}

Как запустить блокирующий код?

Чтобы запустить блокирующий код внутри сопрограммы, используйте Dispatchers.IO , который специально разработан для выгрузки блокирующих задач в общий пул потоков. Кроме того, согласно комментарию @George Leung, метод runInterruptible можно использовать для прерываемого вызова указанного блока, если базовый код реагирует на прерывания потока.

Таким образом, сопрограммы в Kotlin нельзя прервать, а отдельные сопрограммы склонны к голоданию, если другие не работают хорошо, верно?

Stefan Haberl 14.04.2023 08:29

Они не используют механизм прерывания потока Java, и да.

Louis Wasserman 14.04.2023 09:36

По второму вопросу да, абсолютно. Хотя вы можете запустить блокирующий код внутри разработанного для этого диспетчера, например Dispatchers.IO.

Raman 14.04.2023 14:04

Если у вас есть блокирующий код, который можно прервать, вы можете использовать runInterruptible(Dispatchers.IO). Это преобразует отмену сопрограммы в прерывание потока.

George Leung 14.04.2023 21:49

Другие вопросы по теме