【问题标题】:How to interrupt java.util.Scanner nextLine call如何中断 java.util.Scanner nextLine 调用
【发布时间】:2011-06-26 08:47:19
【问题描述】:

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

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

有没有办法中断scanner的阻塞,让它返回?

谢谢

【问题讨论】:

  • 你可以调用scanner.hasNext()而不是scanner.nextLine()这个方法可以根据javadoc阻塞,所以,你可能需要处理它。这个想法是,与scanner.nextLine()不同,scanner.hasNext()不会提前输入,因此您可以在调用scanner.nextLine()之前检查读取线程是否已被另一个线程停止的标志
  • 是的,但这会涉及不断的轮询。
  • 您应该能够在侦听线程上调用 Thread.interrupt,这将导致您可以从 ioException() 方法获得的 InterruptedIOException。不确定它如何与 nextLine() 交互,或者它是否与你的底层输入流一起工​​作,但在大多数情况下它应该终止 nextLine。
  • @josefx 根据我的测试,这实际上不会终止nextLine - 永远。至少不适合我。

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


【解决方案1】:

article 描述了一种在阅读时避免阻塞的方法。它给出了代码 sn-p,您可以按照我在评论中的说明进行修改。

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 类,将本文中描述的逻辑封装起来。

【讨论】:

  • 嘿,谢谢,我希望有一个不涉及主动等待的解决方案。
【解决方案2】:

首先:这不会解决如果有未完成的输入请求(即使已取消),关闭整个程序需要 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
    }

}

【讨论】:

    【解决方案3】:

    当然。使用核弹。在主线程结束时调用System.exit(0)。这会谋杀一切。甚至在 System.in 中等待的活动线程。

    问题在于 System.in 是一个传统的阻塞输入流,当它阻塞时,线程被标记为正在运行。你不能打断它。因此,您用于读取 System.in 的任何线程都在调用 read 并且 read 将阻塞线程。你可以通过一系列技巧来哄骗其中的一些东西,避免调用 read,除非在那些情况下,我们可以确定不会有阻塞,然后不断地轮询。但是,没有真正的方法可以解决这个问题,即任何尝试读取都会锁定您的线程,并且没有多少关闭底层流或中断或停止线程会拯救您。但是,如果你杀死整个虚拟机……线程就会死掉。

    显然,您需要确保其余线程已正确退出,这只是我希望能够响应作为最后挂机的键入输入线程的一个愚蠢。但是,如果情况完全如此,那么正确的答案是退出,或者至少,基本上是唯一一个不会无缘无故地消耗时钟周期并让程序终止的答案。

    【讨论】:

    • 我确实试过了,System.exit(0) 并没有出人意料地阻止它。我不得不kill -9这个过程。甚至killall java 也没有用。
    • 它确实对我有用。但是,遗憾的是,由于这个错误/故障,我相信它很可能是核免疫的。
    猜你喜欢
    • 2014-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 2023-03-21
    相关资源
    最近更新 更多