如何中断java.util.Scanner下一行调用

如何中断java.util.Scanner下一行调用,java,command-line,java.util.scanner,Java,Command Line,Java.util.scanner,我使用的是多线程环境,其中一个线程通过反复调用scanner.nextLine()不断侦听用户输入。 要结束应用程序,另一个线程会停止此runloop,但侦听线程在最后一次用户输入之前不会停止(由于nextLine()的阻塞性质) 关闭流似乎不是一个选项,因为我正在从System.in读取,它返回一个不可关闭的InputStream 是否有办法中断扫描仪的阻塞,使其返回 感谢这描述了一种在阅读时避免阻塞的方法。它给出了代码片段,您可以按照我在注释中的指示对其进行修改 import java.io

我使用的是多线程环境,其中一个线程通过反复调用
scanner.nextLine()
不断侦听用户输入。 要结束应用程序,另一个线程会停止此runloop,但侦听线程在最后一次用户输入之前不会停止(由于
nextLine()
的阻塞性质)

关闭流似乎不是一个选项,因为我正在从
System.in
读取,它返回一个不可关闭的
InputStream

是否有办法中断扫描仪的阻塞,使其返回

感谢

这描述了一种在阅读时避免阻塞的方法。它给出了代码片段,您可以按照我在注释中的指示对其进行修改

import java.io.*;
import java.util.concurrent.Callable;

public class ConsoleInputReadTask implements Callable<String> {
  public String call() throws IOException {
    BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in));
    System.out.println("ConsoleInputReadTask run() called.");
    String input;
    do {
      System.out.println("Please type something: ");
      try {
        // wait until we have data to complete a readLine()
        while (!br.ready()  /*  ADD SHUTDOWN CHECK HERE */) {
          Thread.sleep(200);
        }
        input = br.readLine();
      } catch (InterruptedException e) {
        System.out.println("ConsoleInputReadTask() cancelled");
        return null;
      }
    } while ("".equals(input));
    System.out.println("Thank You for providing input!");
    return input;
  }
}
import java.io.*;
导入java.util.concurrent.Callable;
公共类ConsoleInputReadTask实现可调用{
公共字符串调用()引发IOException{
BufferedReader br=新的BufferedReader(
新的InputStreamReader(System.in));
println(“调用了ConsoleInputReadTask run());
字符串输入;
做{
System.out.println(“请键入内容:”);
试一试{
//等到有数据时再完成readLine()
而(!br.ready()/*在此处添加关机检查*/){
睡眠(200);
}
输入=br.readLine();
}捕捉(中断异常e){
System.out.println(“ConsoleInputReadTask()已取消”);
返回null;
}
}而(“.”等于(输入));
System.out.println(“感谢您的输入!”);
返回输入;
}
}

您可以直接使用这段代码,也可以编写一个新的可关闭的InputStream类,将本文中描述的逻辑封装起来。

当然。使用核武器。在主线程结束时调用
System.exit(0)
。这会毁掉一切。即使是正在System.in中等待的活动线程

问题是System.in是一个带有阻塞的传统输入流,当它阻塞时,线程被标记为正在运行。你不能打断它。因此,无论您使用哪个线程来读取System.in,它都会调用read,而read将阻塞该线程。你可以用一系列技巧来哄骗这些东西,避免调用read,除非在那些情况下,我们可以确保没有阻塞,然后不断地进行轮询。但是,没有任何真正的方法可以解决这个问题,任何试图读取的操作都会锁定线程,关闭底层流或中断或停止线程都无法挽救您的生命。但是,如果你谋杀了整个虚拟机。。。线会死的


显然,您需要确保其余线程已正确退出,而我只希望能够响应最后一个挂机上的类型化输入线程。但是,如果完全是这种情况,正确的答案是退出,或者至少,基本上是唯一一个可以在没有任何原因的情况下运行时钟周期并让程序终止的答案。

首先:这并不能解决关闭整个程序需要系统的问题。exit()如果有未完成的输入请求(即使已取消),则调用。您可以通过向控制台中输入一个按键来规避这个问题,但这完全是另一回事

若要在控制台中执行此操作,则不进行轮询是不可能的,因为实际上不可能取消阻止等待System.in输入的线程,因为System.in本身并没有可中断的get()方法。因此,如果您知道输入不会被阻塞,则不使用轮询只请求输入

如果您真的希望某个东西可以作为控制台的可中断nextLine(),那么您可能应该考虑创建一个Swing窗口或类似的东西,并为其创建一个简单的输入接口。这其实并不难,除了一些边缘情况外,它还具备您所要求的所有功能

然而,我自己也在做这件事,因为我想让线程停止等待System.in的输入,而不关闭程序(同时避免轮询),这就是我在切换到自己的窗口之前想到的

我不能自信地说这是最佳实践,但它应该是线程安全的,似乎工作正常,我想不出任何直接的问题。不过,我想将故障从备用(尽管无法实现)输出切换到实际错误。您可以通过中断线程或调用cancel()来取消活动的输入请求,这将取消当前正在等待的请求

它使用信号量和线程创建一个阻塞nextLine()方法,该方法可以在其他地方中断/取消。取消并不完美——例如,您只能取消当前正在等待的线程的请求,但中断线程应该可以正常工作

package testapp;

/**
 *
 * @author Devlin Grasley
 */
import java.util.concurrent.Semaphore;
import java.util.Scanner;

public class InterruptableSysIn {
    protected static Scanner input = new Scanner (System.in);
    protected static final Semaphore waitingForInput = new Semaphore(0,true); //If InterruptableSysIn is waiting on input.nextLine(); Can also be cleared by cancel();
    protected static String currentLine = ""; //What the last scanned-in line is
    private static final Input inputObject = new Input();
    private static final Semaphore waitingOnOutput = new Semaphore (1); // If there's someone waiting for output. Used for thread safety
    private static boolean canceled = false; //If the last input request was cancled.
    private static boolean ignoreNextLine = false; //If the last cancel() call indicated input should skip the next line.
    private static final String INTERRUPTED_ERROR = "\nInterrupted";
    private static final String INUSE_ERROR = "\nInUse";
    private static boolean lasLineInterrupted = false;

    /**
     * This method will block if someone else is already waiting on a next line.
     * Gaurentees on fifo order - threads are paused, and enter a queue if the
     * input is in use at the time of request, and will return in the order the
     * requests were made
     * @return The next line from System.in, or "\nInterrupted" if it's interrupted for any reason
     */
    public static String nextLineBlocking(){
        //Blocking portion
        try{
            waitingOnOutput.acquire(1);
        }catch(InterruptedException iE){
            return INTERRUPTED_ERROR;
        }
        String toReturn = getNextLine();
        waitingOnOutput.release(1);
        return toReturn;
    }

    /**
     * This method will immediately return if someone else is already waiting on a next line.
     * @return The next line from System.in, or 
     * "\nInterrupted" if it's interrupted for any reason
     * "\nInUse" if the scanner is already in use
     */
    public static String nextLineNonBlocking(){
        //Failing-out portion
        if(!waitingOnOutput.tryAcquire(1)){
            return INUSE_ERROR;
        }
        String toReturn = getNextLine();
        waitingOnOutput.release(1);
        return toReturn;
    }

    /**
     * This method will block if someone else is already waiting on a next line.
     * Gaurentees on fifo order - threads are paused, and enter a queue if the
     * input is in use at the time of request, and will return in the order the
     * requests were made
     * @param ignoreLastLineIfUnused If the last line was canceled or Interrupted, throw out that line, and wait for a new one.
     * @return The next line from System.in, or "\nInterrupted" if it's interrupted for any reason
     */
    public static String nextLineBlocking(boolean ignoreLastLineIfUnused){
        ignoreNextLine = ignoreLastLineIfUnused;
        return nextLineBlocking();
    }

    /**
     * This method will fail if someone else is already waiting on a next line.
     * @param ignoreLastLineIfUnused If the last line was canceled or Interrupted, throw out that line, and wait for a new one.
     * @return The next line from System.in, or 
     * "\nInterrupted" if it's interrupted for any reason
     * "\nInUse" if the scanner is already in use
     */
    public static String nextLineNonBlocking(boolean ignoreLastLineIfUnused){
        ignoreNextLine = ignoreLastLineIfUnused;
        return nextLineNonBlocking();
    }

    private static String getNextLine(){
        String toReturn = currentLine; //Cache the current line on the very off chance that some other code will run etween the next few lines

        if(canceled){//If the last one was cancled
            canceled = false;

            //If there has not been a new line since the cancelation
            if (toReturn.equalsIgnoreCase(INTERRUPTED_ERROR)){
                //If the last request was cancled, and has not yet recieved an input

                //wait for that input to finish
                toReturn = waitForLineToFinish();
                //If the request to finish the last line was interrupted
                if(toReturn.equalsIgnoreCase(INTERRUPTED_ERROR)){
                    return INTERRUPTED_ERROR;
                }

                if(ignoreNextLine){
                    //If the last line is supposed to be thrown out, get a new one
                    ignoreNextLine = false;
                    //Request an input
                    toReturn = getLine();
                }else{
                    return toReturn;
                }

            //If there has been a new line since cancelation
            }else{
                //If the last request was cancled, and has since recieved an input
                try{
                    waitingForInput.acquire(1); //Remove the spare semaphore generated by having both cancel() and having input
                }catch(InterruptedException iE){
                    return INTERRUPTED_ERROR;
                }

                if(ignoreNextLine){
                    ignoreNextLine = false;
                    //Request an input
                    toReturn = getLine();
                }
                //return the last input
                return toReturn;
            }
        }else{
            if(lasLineInterrupted){

                //wait for that input to finish
                toReturn = waitForLineToFinish();
                //If the request to finish the last line was interrupted
                if(toReturn.equalsIgnoreCase(INTERRUPTED_ERROR)){
                    return INTERRUPTED_ERROR;
                }

                //Should the read be thrown out?
                if(ignoreNextLine){
                    //Request an input
                    toReturn = getLine();
                }

            }else{
                ignoreNextLine = false; //If it's been set to true, but there's been no cancaleation, reset it.

                //If the last request was not cancled, and has not yet recieved an input
                //Request an input
                toReturn = getLine();
            }
        }
        return toReturn;
    }

    private static String getLine (){
        Thread ct = new Thread(inputObject);
        ct.start();
        //Makes this cancelable
        try{
            waitingForInput.acquire(1); //Wait for the input
        }catch(InterruptedException iE){
            lasLineInterrupted = true;
            return INTERRUPTED_ERROR;
        }
        if(canceled){
            return INTERRUPTED_ERROR;
        }
        return currentLine;
    }

    public static String waitForLineToFinish(){
        //If the last request was interrupted
        //wait for the input to finish
        try{
            waitingForInput.acquire(1);
            lasLineInterrupted = false;
            canceled = false;
            return currentLine;
        }catch(InterruptedException iE){
            lasLineInterrupted = true;
            return INTERRUPTED_ERROR;
        }
    }

    /**
     * Cancels the currently waiting input request
     */
    public static void cancel(){
        if(!waitingOnOutput.tryAcquire(1)){ //If there is someone waiting on user input
            canceled = true;
            currentLine = INTERRUPTED_ERROR;
            waitingForInput.release(1); //Let the blocked scanning threads continue, or restore the lock from tryAquire()    
        }else{
            waitingOnOutput.release(1); //release the lock from tryAquire()    
        }
    }

    public static void cancel(boolean throwOutNextLine){
        if(!waitingOnOutput.tryAcquire(1)){ //If there is someone waiting on user input
            canceled = true;
            currentLine = INTERRUPTED_ERROR;
            ignoreNextLine = throwOutNextLine;
            waitingForInput.release(1); //Let the blocked scanning threads continue
        }else{
            waitingOnOutput.release(1); //release the lock from tryAquire()    
        }
    }

}

class Input implements Runnable{
    @Override
    public void run (){
        InterruptableSysIn.currentLine = InterruptableSysIn.input.nextLine();
        InterruptableSysIn.waitingForInput.release(1); //Let the main thread know input's been read
    }

}

您可以调用
scanner.hasNext()
而不是
scanner.nextLine()
此方法可能会根据javadoc进行阻塞,因此,您可能需要处理此问题。与
scanner.nextLine()
不同,
scanner.hasNext()
不提前输入,因此在调用
scanner.nextLine()
yes之前,如果读取线程已被另一个线程停止,则可以检查标志,但这将涉及到持续轮询。您应该能够在侦听线程上调用Thread.interrupt,这将导致从ioException()方法获得的InterruptedIOException。不确定它是如何与nextLine()交互的,或者它是否与您的底层inputstream一起工作的,但在大多数情况下它应该终止nextLine。@根据我的测试,它实际上从未终止过
nextLine
。至少对我来说不是。嘿,谢谢,我希望找到一个不涉及主动等待的解决方案。我实际上尝试了这个,并且
System.exit(0)
并没有意外地阻止它。我不得不
kill-9
这个过程。甚至
killall java