How to interrupt BufferedReader's readLine

后端 未结 9 1276
醉梦人生
醉梦人生 2020-11-29 07:56

I am trying to read input from a socket line by line in multiple threads. How can I interrupt readLine() so that I can gracefully stop the thread that it\'s bl

相关标签:
9条回答
  • 2020-11-29 08:22

    I was playing around with this recently (using Scala), and I didn't like the accepted answer of closing the socket and getting an exception.

    Eventually I discovered that it's possible to call socket.shutdownInput() in the interrupting thread to get out of the readLine call without an exception. I make this call in a SIGINT handler so that I can clean up and close the socket in the main thread.

    Note, that the equivalent exists for the outputstream with socket.shutdownOutput()

    0 讨论(0)
  • 2020-11-29 08:24

    Sorry for being over 6 years late ;-) I had a need for some interruptible readLine when reading from the keyboard, for a simple hobby console application. In other words, I couldn't "close the socket".

    As you may know, System.in is an InputStream that apparently already does some buffering (you need to press Enter]). However, it seems to be suggested to wrap it in a BufferedReader for better efficiency, so my input is from:

    BufferedReader consoleIn = new BufferedReader(new InputStreamReader(System.in));

    The other thing one might have discovered is that BufferedReader.readLine() blocks until input is provided (even if the thread is interrupted, which seems to only end the thread once readline() gets its input). It is however possible to predict when BufferedReader.read() will not block, by calling BufferedReader.ready() == true. (However, == false does not guarantee a block, so beware.)

    So I have incorporated the above ideas into a method that reads the BufferedReader character by character, checking in between each character if the thread has been interrupted, and also checks for end-of-line, at which point the line of text is returned.

    You may find this code useful, pass the consoleIn variable as declared above. (Criticism may be welcomed too...):

    private String interruptibleReadLine(BufferedReader reader)
            throws InterruptedException, IOException {
        Pattern line = Pattern.compile("^(.*)\\R");
        Matcher matcher;
        boolean interrupted = false;
    
        StringBuilder result = new StringBuilder();
        int chr = -1;
        do {
            if (reader.ready()) chr = reader.read();
            if (chr > -1) result.append((char) chr);
            matcher = line.matcher(result.toString());
            interrupted = Thread.interrupted(); // resets flag, call only once
        } while (!interrupted && !matcher.matches());
        if (interrupted) throw new InterruptedException();
        return (matcher.matches() ? matcher.group(1) : "");
    }
    

    ... And in the thread that is calling this, catch the exceptions and end the thread appropriately.

    This was tested in Java 8 on Linux.

    0 讨论(0)
  • 2020-11-29 08:26

    A sketch for a solution might be this: NIO provides methods for nonblocking IO, so you have to implement something called Foo that uses nonblocking NIO on the socket end but also provides a InputStream or Reader interface on the other end. If the BufferedReader enters its own read, it will call Foo, which will call Selector.select with read intent. select will either return indicating the presence of more data or it will block until more data is available.

    If another thread wants to unblock the reader, it must call Selector.wakeup and the selector can return gracefully by throwing an exception the by BufferedReader.

    The socket should be still open after that.

    Variation A: call Selector.select(timeout) to do busy polling light.

    0 讨论(0)
  • 2020-11-29 08:27

    If you want to use readLine on a server socket within a client-server tcp architecture, for instance, you can use setSoTimeout(int timeout) of java.net.Socket.

    From the Socket#setSoTimeout(int timeout) Documentation:

    Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid.

    public class MainApp {
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            ServerSocket serverSocket = new ServerSocket(11370);
            Socket clientSocket = serverSocket.accept();
            clientSocket.setSoTimeout(2000);
            executorService.execute(new ReadingThread(clientSocket));
            // ... some async operations
            executorService.shutdown();
        }
    }
    
    public class ReadingThread implements Runnable {
        private final Socket clientSocket;
        public ReadingThread(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
    
        @Override
        public void run() {
            BufferedReader socketReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String readInput = null;
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    readInput = socketReader.readLine();
                } catch (SocketTimeoutException e) {
                    continue; 
                }
            }
            // operations with readInput
        }
    }
    

    The main application implements a server socket which listens to connections and has a thread pool. If an incoming client communication is accepted, then a new Thread from the pool is assigned and the run function is invoked in ReadingThread (can be adjusted to allow multiple threads). On the socket used for communicating to the client the property setSoTimeout(int timeout) has been set. Therefore if readLine does not return within the specified timeout a SocketTimeoutException is thrown. You can check in a loop whether the ReadingThread has been interrupted by the main application, and if so stop reading from the socket.

    0 讨论(0)
  • 2020-11-29 08:30

    Close the socket on the interrupting thread. This will cause an exception to be thrown on the interrupted thread.

    For more information on this and other concurrency issues, I highly recommend Brian Goetz's book "Java Concurrency in Practice".

    0 讨论(0)
  • 2020-11-29 08:38

    I think that you might have to use something other than readLine(). You could use read() and at every loop iteration check to see if the thread was interrupted and break out of the loop if it was.

    BufferedReader reader = //...
    int c;
    while ((c = reader.read()) != -1){
      if (Thread.isInterrupted()){
        break;
      }
      if (c == '\n'){
        //newline
      }
      //...
    }
    
    0 讨论(0)
提交回复
热议问题