Запуск неблокирующих сопрограмм в основном потоке

У нас есть особый вариант использования, и мне нужна помощь, чтобы выяснить, сможем ли мы решить нашу проблему с помощью сопрограмм 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.

Спасибо

мы должны находиться в основном потоке, потому что данные методы не являются потокобезопасными. - Fyi, метод, не являющийся «потокобезопасным», не означает, что он должен вызываться в основном потоке. Кроме того, с сопрограммами вы указываете не поток для его запуска, а диспетчер / контекст
Tim 07.11.2018 12:00

Ты прав. Это означает, что объекты и их методы не предназначены для использования несколькими потоками, и поскольку сервер работает в основном потоке (который мы можем назвать серверным потоком, если хотите), мы должны обращаться к ним из того же потока, который вызывает наши методы.

K. D. 07.11.2018 12:03

Что ж, с сопрограммами вы можете делать именно то, что делаете в своем первом фрагменте кода. Вы выполняете код «не в основном потоке» (не блокируя, а приостанавливая), например. с launch(Dispatchers.IO), и когда это будет сделано, вы продолжите поток, который вы начали, без необходимости переключаться обратно вручную. Вы можете указать, что главный диспетчер будет тем, на котором изначально запускаются сопрограммы.

Tim 07.11.2018 12:08

Спасибо за вашу помощь. Однако я не совсем понимаю вашу точку зрения. Если я заменю launch на launch(Dispatchers.IO), ничего особенного не изменится. Код по-прежнему выполняется в порожденном потоке, а не в потоке, откуда поступил первоначальный вызов.

K. D. 07.11.2018 12:17

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

Yoni Gibbs 07.11.2018 12:19

Хорошо, IIRC, вы можете запустить Dispatchers.Unconfined, если вы хотите запустить сопрограмму в вызывающем потоке, см. github.com/Kotlin/kotlinx.coroutines/blob/master/docs/…

Tim 07.11.2018 12:20

@YoniGibbs да, как в первом фрагменте. @TimCastelijns при использовании Unconfined вызов .join() на CompletableFuture блокирует вызывающий поток и, следовательно, основной поток, что замораживает сервер до тех пор, пока не будет решено будущее.

K. D. 07.11.2018 12:23

Не могли бы вы преобразовать сопрограмму в CompletableFuture, а затем вызвать thenAcceptAsync, передав исполнитель, который будет запускать код в вашем основном потоке? например CoroutineScope(Dispatchers.IO).future { `// В фоновом потоке - весь код здесь может использовать доброту сопрограмм` `" какое-то возвращаемое значение "` }.thenAcceptAsync({ `// В основном потоке, предоставляемом myMainThreadExecutor)` }, myMainThreadExecutor) (К сожалению, не могу понять, как отформатировать код в комментарии, чтобы сделать его читабельным.)

Yoni Gibbs 07.11.2018 12:40

Что ж, db.fetchBalance уже является CompletableFuture. Я не тестировал ваш код, но похоже, что он закончится чем-то вроде моего первого фрагмента, и тогда это вообще не будет улучшением. Или я ошибаюсь? @YoniGibbs

K. D. 07.11.2018 12:51

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

Yoni Gibbs 07.11.2018 12:55

Спасибо, но я полагаю, что эта попытка не улучшит наши варианты использования, поскольку моя идея заключалась в том, чтобы уменьшить шаблонность / вложенность и обеспечить лучшую обработку исключений. Однако, если наши требования - слишком особенный, CompletableFutures пока в порядке.

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

Ответы 2

Попробуйте функцию 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/…

bashizip 14.01.2020 23:43

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