Как отменить 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%. Это подходит для такого интерактивного приложения.