У нас есть особый вариант использования, и мне нужна помощь, чтобы выяснить, сможем ли мы решить нашу проблему с помощью сопрограмм Kotlin или нам нужно полагаться на CompletableFutures.
По сути, мы пишем плагины для однопоточного сервера. Это означает, что мы можем использовать разные перехватчики для добавления логики, и эта логика всегда выполняется в основном потоке, который нельзя блокировать. Кроме того, при использовании API сервера мы должны находиться в основном потоке, потому что данные методы не являются потокобезопасными.
Чтобы заставить это работать с асинхронным кодом, мы использовали планировщик сервера для создания системы производителя / потребителя, которая выполняет асинхронные задачи в фоновом режиме и синхронизирует возвращает результаты в основной поток сервера. Реализация не должна быть такой важной, поэтому вот лишь пример того, как это выглядит на практике:
// execute hook that runs when a user on the server runs a command
override fun execute(sender: CommandSender, args: Array<out String>) {
// call comes from the main thread
db.fetchBalance(sender.name)
// fetchBalance runs asynchronous code without blocking
// the current thread by utilizing a consumer/producer system
.thenAccept {
// the CompletableFuture is resolved after completion
// here we are in the main thread again, so that we can access
// server methods in a thread safe manner
sender.sendMessage("Your balance: $it")
}
}
Теперь мой вопрос: можно ли заменить приведенный выше пример кодом Kotlin, который делает его более читабельным, например async / await в JavaScript. Помните, что в JavaScript мы можем сделать это:
async function onBalanceRequest(client, name) {
let balance = await db.fetchBalance(name);
client.sendMessage("Your money: " + balance);
}
Несколько дней назад я задал аналогичный вопрос относительно async / await, что привело к решению, которое может выглядеть так:
private fun onBalanceRequest(sender: CommandSender) {
// call comes from the main thread
GlobalScope.launch {
// here we are within a new thread
val money = db.fetchBalance(sender.name).join()
// here we are within the same thread, which is
// not the main thread, so the code below isn't safe
sender.sendMessage("Your balance: $money")
}
}
Как описано в комментариях, проблема в том, что после «ожидания будущего» код запускается в потоке сопрограммы. Итак, мой вопрос: сможем ли мы достичь чего-то подобного, что я описал, с помощью сопрограмм, или они просто не были созданы для этого варианта использования. Я читал о возможности указать поток для порожденной сопрограммы, но тогда этот поток будет заблокирован, так что это не сработает.
Если CompletableFutures - единственный способ решить эту проблему с помощью решать, мы будем придерживаться их, но я хотел попробовать сопрограммы, поскольку они выглядят лучше для написания и обработки, чем для CompletableFutures.
Спасибо
Ты прав. Это означает, что объекты и их методы не предназначены для использования несколькими потоками, и поскольку сервер работает в основном потоке (который мы можем назвать серверным потоком, если хотите), мы должны обращаться к ним из того же потока, который вызывает наши методы.
Что ж, с сопрограммами вы можете делать именно то, что делаете в своем первом фрагменте кода. Вы выполняете код «не в основном потоке» (не блокируя, а приостанавливая), например. с launch(Dispatchers.IO), и когда это будет сделано, вы продолжите поток, который вы начали, без необходимости переключаться обратно вручную. Вы можете указать, что главный диспетчер будет тем, на котором изначально запускаются сопрограммы.
Спасибо за вашу помощь. Однако я не совсем понимаю вашу точку зрения. Если я заменю launch на launch(Dispatchers.IO), ничего особенного не изменится. Код по-прежнему выполняется в порожденном потоке, а не в потоке, откуда поступил первоначальный вызов.
Итак, вы хотите, чтобы основной поток запускал сопрограмму, эта сопрограмма запускается в фоновом потоке, и когда он завершается, он вызывает некоторый метод (возможно, передавая значение, вычисленное в сопрограмме), и метод, который он вызывает, выполняется вернуться в основную ветку?
Хорошо, IIRC, вы можете запустить Dispatchers.Unconfined, если вы хотите запустить сопрограмму в вызывающем потоке, см. github.com/Kotlin/kotlinx.coroutines/blob/master/docs/…
@YoniGibbs да, как в первом фрагменте. @TimCastelijns при использовании Unconfined вызов .join() на CompletableFuture блокирует вызывающий поток и, следовательно, основной поток, что замораживает сервер до тех пор, пока не будет решено будущее.
Не могли бы вы преобразовать сопрограмму в CompletableFuture, а затем вызвать thenAcceptAsync, передав исполнитель, который будет запускать код в вашем основном потоке? например CoroutineScope(Dispatchers.IO).future { `// В фоновом потоке - весь код здесь может использовать доброту сопрограмм` `" какое-то возвращаемое значение "` }.thenAcceptAsync({ `// В основном потоке, предоставляемом myMainThreadExecutor)` }, myMainThreadExecutor) (К сожалению, не могу понять, как отформатировать код в комментарии, чтобы сделать его читабельным.)
Что ж, db.fetchBalance уже является CompletableFuture. Я не тестировал ваш код, но похоже, что он закончится чем-то вроде моего первого фрагмента, и тогда это вообще не будет улучшением. Или я ошибаюсь? @YoniGibbs
Нет, вы правы, будет очень похоже. Но его преимущество в том, что код внутри блока future может использовать сопрограммы. Поэтому, если этот код в конечном итоге вызывает сложную серию других функций, все из которых вы хотите не блокировать, вы можете написать все это гораздо более идиоматически, используя сопрограммы, а не иметь множество завершаемых фьючерсов.
Спасибо, но я полагаю, что эта попытка не улучшит наши варианты использования, поскольку моя идея заключалась в том, чтобы уменьшить шаблонность / вложенность и обеспечить лучшую обработку исключений. Однако, если наши требования - слишком особенный, CompletableFutures пока в порядке.





Попробуйте функцию withContext. Оберните в него свой код, и он будет выполнен в желаемом контексте.
Например:
withContext(Dispatchers.Main) {
//This will run in Main Thread
}
Вы можете заменить Dispatchers.Main на CoroutinesContext по вашему выбору
Примечание: функция withContext является функцией «приостановки» и должна выполняться только в Coroutine Scope.
Немедленное выполнение
Что, если вам нужно, чтобы это произошло немедленно? Для этого вы можете использовать
Dispatchers.Main.immediate
Выполняет сопрограммы немедленно, когда они уже находятся в правильном контексте
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch(Dispatchers.Main.immediate) {
log("A")
}
log("B")
}
Распечатать
OUTPUT:
// A
// B
Вы должны цитировать свои источники: medium.com/@trionkidnapper/…