Как отменить ConsoleReader.readLine()
Прежде всего, я изучаю scala и новый для java-мира.
Я хочу создать консоль и запустить эту консоль в качестве службы, которую вы могли бы запустить и остановить.
Я смог запустить ConsoleReader в Актер, но я не знаю, как правильно остановить ConsoleReader.
Вот код:
import eu.badmood.util.trace
import scala.actors.Actor._
import tools.jline.console.ConsoleReader
object Main {
def main(args:Array[String]){
//start the console
Console.start(message => {
//handle console inputs
message match {
case "exit" => Console.stop()
case _ => trace(message)
}
})
//try to stop the console after a time delay
Thread.sleep(2000)
Console.stop()
}
}
object Console {
private val consoleReader = new ConsoleReader()
private var running = false
def start(handler:(String)=>Unit){
running = true
actor{
while (running){
handler(consoleReader.readLine("\33[32m> \33[0m"))
}
}
}
def stop(){
//how to cancel an active call to ConsoleReader.readLine ?
running = false
}
}
Я также ищу советы относительно этого кода!
Ответы
Ответ 1
Основной вызов для чтения символов из ввода блокируется. На платформе, отличной от Windows, она будет использовать System.in.read()
, а в Windows она будет использовать org.fusesource.jansi.internal.WindowsSupport.readByte
.
Таким образом, ваша задача - заставить этот блокирующий вызов вернуться, когда вы хотите остановить свою консольную службу. См. http://www.javaspecialists.eu/archive/Issue153.html и Можно ли читать из InputStream с таймаутом? для некоторых идей... После того, как вы это выясните, read
верните -1
, когда ваша консольная служба остановится, чтобы ConsoleReader
подумал об этом. Вам понадобится ConsoleReader
, чтобы использовать вашу версию этого вызова:
- Если вы находитесь в Windows, вам, вероятно, придется переопределить
tools.jline.AnsiWindowsTerminal
и использовать конструктор ConsoleReader
, который принимает Terminal
(иначе AnsiWindowsTerminal
будет просто использовать WindowsSupport.readByte` напрямую)
- В unix существует один конструктор
ConsoleReader
, который принимает InputStream
, вы можете предоставить свою собственную оболочку вокруг System.in
Еще несколько мыслей:
- Теперь есть объект
scala.Console
, поэтому для меньшего путаницы имя твое по-другому.
-
System.in
- это уникальный ресурс, поэтому вам, вероятно, необходимо обеспечить, чтобы только один вызывающий абонент использовал Console.readLine
за раз. Прямо сейчас start
будет напрямую вызывать readLine
, а несколько вызывающих абонентов могут вызвать start
. Вероятно, служба консоли может readLine
и поддерживать список обработчиков.
Ответ 2
Предполагая, что ConsoleReader.readLine отвечает на прерывание потока, вы можете переписать Консоль, чтобы использовать Thread, который вы могли бы затем прервать, чтобы остановить его.
object Console {
private val consoleReader = new ConsoleReader()
private var thread : Thread = _
def start(handler:(String)=>Unit) : Thread = {
thread = new Thread(new Runnable {
override def run() {
try {
while (true) {
handler(consoleReader.readLine("\33[32m> \33[0m"))
}
} catch {
case ie: InterruptedException =>
}
}
})
thread.start()
thread
}
def stop() {
thread.interrupt()
}
}
Ответ 3
Вы можете перезаписать свой InputStream ConsoleReader. ИМХО это разумно, потому что STDIN - это "медленный" поток. Пожалуйста, улучшите пример для ваших нужд. Это только эскиз, но он работает:
def createReader() =
terminal.synchronized {
val reader = new ConsoleReader
terminal.enableEcho()
reader.setBellEnabled(false)
reader.setInput(new InputStreamWrapper(reader.getInput())) // turn on InterruptedException for InputStream.read
reader
}
с оболочкой InputStream:
class InputStreamWrapper(is: InputStream, val timeout: Long = 50) extends FilterInputStream(is) {
@tailrec
final override def read(): Int = {
if (is.available() != 0)
is.read()
else {
Thread.sleep(timeout)
read()
}
}
}
P.S. Я пытался использовать NIO - много проблем с System.in(особенно кроссплатформенными). Я вернулся к этому варианту. Нагрузка процессора составляет около 0%. Это подходит для такого интерактивного приложения.