如何中断java.util.Scanner nextLine调用

时间:2011-02-13 07:44:24

标签: java command-line interrupt java.util.scanner

我正在使用多线程环境,一个Thread通过反复调用scanner.nextLine()来不断监听用户输入。 要结束应用程序,此runloop由另一个线程停止,但监听线程将不会停止,直到最后一个用户输入(由于nextLine()的阻塞性质)。

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

有没有办法打断扫描仪的阻塞,以便它会返回?

感谢

3 个答案:

答案 0 :(得分:8)

article描述了一种在阅读时避免阻止的方法。它给出了代码片段,您可以根据我在评论中指出的内容进行修改。

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;
  }
}

您可以直接使用此代码,也可以编写一个新的可关闭的InputStream类,包含本文中描述的逻辑。

答案 1 :(得分:0)

首先要解决:如果存在未完成的输入请求(即使已取消),也无法解决关闭整个程序需要System.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
    }

}

答案 2 :(得分:-1)

不确定。使用核武器。在主线程结束时调用System.exit(0)。这将谋杀一切。甚至活动线程在System.in中等待。

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

显然你需要确保其余的线程都已正确退出,这只是一个愚蠢的我希望能够响应最后一个hanger-on的类型输入线程。但是,如果情况完全如此,那么正确的答案就是退出,或者至少基本上唯一的答案就是在没有理由烧掉时钟周期的情况下工作,并让程序终止。