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

发布于 2024-10-17 04:42:52 字数 270 浏览 4 评论 0原文

我正在使用多线程环境,其中一个线程通过重复调用 scanner.nextLine() 不断侦听用户输入。 要结束应用程序,此运行循环将由另一个线程停止,但侦听线程直到最后一次用户输入后才会停止(由于 nextLine() 的阻塞性质)。

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

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

谢谢

I am using a multi threaded environment were one Thread is constantly listening for user input by repeatedly calling scanner.nextLine().
To end the application, this runloop is stopped by another thread, but the listening thread won't stop until a last user input was made (due to the blocking nature of nextLine()).

Closing the stream seems not to be an option since I am reading from System.in, which returns an InputStream that is not closable.

Is there a way to interrupt the blocking of scanner, so that it will return?

thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

原来分手还会想你 2024-10-24 04:42:53

首先:这并不能解决这样的问题:如果存在未完成的输入请求(即使被取消),则需要调用 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
    }

}

To start off with: this will not solve the issue that, to close the whole program, requires a System.exit() call if there has been an unfulfilled input request (even if canceled). You could potentially circumvent this by spoofing a keystroke into console, but that's a whole other ball park.

If you want to do it in console, is impossible to do without polling, as it's impossible to actually un-block a thread waiting for input from System.in, as System.in itself does not have interruptible get() methods. Because of this, without using polling to only request input if you know it will not be blocking.

If you truely want something that will act as an interruptible nextLine() for a console, you should probably look into making a Swing window, or the like, and making a simple input interface for it. This isn't really difficult, and would have all the functionality you're asking for, outside of some edge cases.

However, I was working on this myself, as I wanted a way for a thread to stop waiting for input from System.in, without closing the program (and while avoiding polling), and this is what I came up with, before switching to my own window.

I can't say with any confidence it's best-practice, but it should be thread-safe, seems to be working fine, and I can't think of any immediate issues. I would like to switch failures from alternate (albeit, otherwise unobtainable) outputs, to actual errors though. You can cancel active requests for input either by interrupting the thread, or by calling cancel(), which canceles the currently waiting request.

It uses Semaphores and threads to create a blocking nextLine() method that can be interrupted/canceled elsewhere. Canceling is not perfect - you can only cancel the currently waiting thread's request, for instance, but interrupting threads should work fine.

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
    }

}
淡忘如思 2024-10-24 04:42:53

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

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

显然,您需要确保其余线程已正确退出,而这只是一个愚蠢的事情,我希望能够响应最后一个挂机的键入输入线程。但是,如果完全是这种情况,正确的答案是退出,或者至少,基本上是唯一可以在不无故燃烧时钟周期的情况下工作并让程序终止的答案。

Sure. Use a nuke. Call System.exit(0) at the end of your main thread. This will murder everything. Even the active thread waiting in System.in.

The problem is that System.in is a traditional input stream with blocking, and when it's blocking the thread is marked as running. You cannot interrupt it. So whatever thread you are using to read the System.in is calling read and the read will block the thread. You can coax some of this stuff with a bunch of tricks avoid calling read except in those cases when we can be sure there will be no block and then constantly poll. But, there's no real way around the problem that any attempt to read that will lock your thread and no amount of closing underlying streams or interrupting or stopping the thread will save you. But, if you murder the entire vm... the thread will die.

Obviously you need to make sure the rest of the threads have properly exited and it's just that one stupid I want to be able to respond to typed input thread that is the last hanger-on. But, if that's totally the case the correct answer is to exit, or at least, basically the only answer that'll work without burning clock-cycles for no reason and let the program terminate.

如果没有你 2024-10-24 04:42:52

这篇文章描述了一种避免阅读时阻塞的方法。它提供了代码片段,您可以按照我在评论中指出的方式进行修改。

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

This article describes an approach to avoiding blocking when reading. It gives the code snippet, which you could amend as I indicate in a comment.

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

You could either use this code directly, or write a new closable InputStream class, wrapping up the logic described in this article.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文