Котлин 1.3: как выполнить блок в отдельном потоке?

Я читал о параллельности в Kotlin и думал, что начал понимать это... Тогда я обнаружил, что async() устарел в 1.3, и я вернулся к началу.

Вот что я хотел бы сделать: создать поток (и, к сожалению, это должен быть поток, а не управляемый пул), а затем иметь возможность выполнять асинхронные блоки в этом потоке и возвращать Deferred случаи, которые позволят мне использовать .await(),

Каков рекомендуемый способ сделать это в Котлине?

3 ответа

Решение

1. Однопоточный диспетчер сопрограмм

Вот что я хотел бы сделать: создать поток (к сожалению, это должен быть поток, а не управляемый пул)

Запуск необработанного потока для обработки ваших сопрограмм возможен только в том случае, если вы готовы углубиться и внедрить собственный диспетчер сопрограмм для этого случая. Kotlin предлагает поддержку для ваших требований через однопоточный сервис-исполнитель, включенный в диспетчер. Обратите внимание, что это все еще оставляет вам почти полный контроль над тем, как вы запускаете поток, если вы используете перегрузку, которая принимает фабрику потока:

val threadPool = Executors.newSingleThreadExecutor {
    task -> Thread(task, "my-background-thread")
}.asCoroutineDispatcher()

2. async-await против withContext

и затем иметь возможность выполнять асинхронные блоки в этом потоке и возвращать отложенные экземпляры, которые позволят мне использовать.await().

Убедитесь, что вам действительно нужно async-await Это означает, что вам нужно что-то еще, чем

val result = async(singleThread) { blockingCal() }.await()

использование async-await только если вам нужно запустить фоновую задачу, сделайте еще кое-что в вызывающем потоке, и только тогда await() в теме.

Большинство пользователей, плохо знакомых с сопрограммами, фиксируют этот механизм из-за его знакомства с другими языками и используют его для простого последовательного кода, как описано выше, но избегают ловушки блокировки потока пользовательского интерфейса. У Kotlin есть философия "последовательный по умолчанию", которая означает, что вы должны вместо этого использовать

val result = withContext(singleThread) { blockingCall() }

Это не запускает новую сопрограмму в фоновом потоке, но переносит выполнение текущей сопрограммы на нее и обратно, когда это будет сделано.

3. Устаревший на высшем уровне async

Затем я обнаружил, что async() устарела в 1.3

Создание автономных фоновых задач, как правило, нецелесообразно, поскольку в случае ошибок или даже необычных шаблонов выполнения оно не работает должным образом. Ваш метод вызова может вернуться или потерпеть неудачу без await на его результат, но фоновая задача будет продолжаться. Если приложение повторно вводит код, который порождает фоновую задачу, ваш singleThread очередь исполнителя будет расти без ограничений. Все эти задачи будут выполняться без цели, потому что их запросчик давно исчез.

Вот почему Kotlin отказался от сборщиков сопрограмм верхнего уровня, и теперь вы должны явно квалифицировать их с областью сопрограмм, время жизни которой вы должны определить в соответствии с вашим вариантом использования. Когда срок действия области заканчивается, она автоматически отменяет все сопрограммы, созданные в ней.

На примере Android это будет означать привязку области сопрограммы к времени жизни Activity, как объяснено в KDoc CoroutineScope,

Как указано в сообщении, он устарел в пользу вызова async с явной областью, как GlobalScope.async {} вместо.

Это и есть фактическая реализация устаревшего метода.

Убрав верхний уровень async функция, вы не столкнетесь с проблемами с неявными областями или неправильным импортом.

Позвольте мне рекомендовать это решение: сопрограммы Kotlin с возвращенным значением

Он распараллеливает задачи в 3 фоновых потока (так называемый "пул триплетов"), но его легко изменить на однопоточный в соответствии с вашими требованиями, заменив tripletsPool на backgroundThread, как показано ниже:

private val backgroundThread = ThreadPoolExecutor(1, 1, 5L, TimeUnit.SECONDS, LinkedBlockingQueue())
Другие вопросы по тегам