Запуск неблокирующих сопрограмм в главном потоке - PullRequest
0 голосов
/ 07 ноября 2018

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

Спасибо

Ответы [ 2 ]

0 голосов
/ 10 апреля 2019

Выполнение немедленно

Что, если вам нужно, чтобы он сразу же был выполнен? Для этого вы можете использовать

Dispatchers.Main.immediate

Выполняет сопрограммы немедленно, когда она уже находится в нужном контексте

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    launch(Dispatchers.Main.immediate) {
        log("A")
    }

    log("B")
}

Печать

OUTPUT:
// A
// B
0 голосов
/ 07 ноября 2018

Попробуйте withContext функцию. Оберните ваш код в него, и он будет выполнен в нужном контексте.

Например:

withContext(Dispatchers.Main) {
    //This will run in Main Thread
}

Вы можете заменить Dispatchers.Main на CoroutinesContext по вашему выбору

Примечание: withContext функция является «приостановленной» функцией и должна выполняться только в Coroutine Scope

...