简体   繁体   English

如何中断 BufferedReader 的 readLine

[英]How to interrupt BufferedReader's readLine

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 blocking?如何中断readLine()以便我可以优雅地停止它阻塞的线程?

EDIT (bounty) : Can this be done without closing the socket?编辑(赏金) :这可以在不关闭套接字的情况下完成吗?

Without closing the socket:不关闭套接字:

The difficult problem isn't the BufferedReader.readLine , but the underlying read .困难的问题不是BufferedReader.readLine ,而是底层的read If a thread is blocked reading, the only way to get it going is to supply some actual data or close the socket (interrupting the thread probably should work, but in practice does not).如果线程被阻塞读取,唯一的方法是提供一些实际数据或关闭套接字(中断线程可能应该起作用,但实际上不会)。

So the obvious solution is to have two threads.所以显而易见的解决方案是有两个线程。 One that reads the raw data, and will remain blocked.一种读取原始数据,并将保持阻塞状态。 The second, will be the thread calling readLine .第二个,将是调用readLine的线程。 Pipe data from the first the second.管道数据从第一个到第二个。 You then have access to a lock than can be used to wakeup the second thread, and have it take appropriate action.然后,您可以访问可用于唤醒第二个线程的锁,并让它采取适当的操作。

There are variations.有变化。 You could have the first thread using NIO, with a single thread instance shared between all consumers.您可以使用 NIO 拥有第一个线程,并在所有使用者之间共享一个线程实例。

Alternatively you could write a readLine that works with NIO.或者,您可以编写一个与 NIO 一起使用的readLine This could even take aa relatively simple single-threaded form, as Selector.wakeup exists and works.这甚至可以采用相对简单的单线程形式,因为Selector.wakeup存在且有效。

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".关于这个和其他并发问题的更多信息,我强烈推荐 Brian Goetz 的书“Java Concurrency in Practice”。

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.很抱歉迟到了 6 年以上;-) 我需要一些可中断的 readLine 从键盘上阅读时需要一个简单的爱好控制台应用程序。 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 ]).您可能知道, System.in是一个InputStream ,它显然已经做了一些缓冲(您需要按Enter ])。 However, it seems to be suggested to wrap it in a BufferedReader for better efficiency, so my input is from:但是,似乎建议将其包装在BufferedReader以提高效率,因此我的输入来自:

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).人们可能发现的另一件事是BufferedReader.readLine()阻塞,直到提供输入(即使线程被中断,这似乎只在readline()获得输入后才结束线程)。 It is however possible to predict when BufferedReader.read() will not block , by calling BufferedReader.ready() == true .然而,可以通过调用BufferedReader.ready() == true来预测BufferedReader.read()何时不会阻塞 (However, == false does not guarantee a block , so beware.) (但是, == false不能保证一个,所以要小心。)

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.因此,我将上述想法合并到一个方法中,该方法逐个字符读取BufferedReader ,检查每个字符之间是否线程被中断,并检查行尾,此时返回文本行。

You may find this code useful, pass the consoleIn variable as declared above.您可能会发现此代码很有用,传递上面声明的consoleIn变量。 (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.这是在 Linux 上的 Java 8 中测试的。

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.我最近在玩这个(使用 Scala),我不喜欢关闭套接字并获得异常的公认答案。

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.最终我发现可以在中断线程中调用socket.shutdownInput()以无异常地退出 readLine 调用。 I make this call in a SIGINT handler so that I can clean up and close the socket in the main thread.我在 SIGINT 处理程序中进行此调用,以便我可以清理和关闭主线程中的套接字。

Note, that the equivalent exists for the outputstream with socket.shutdownOutput()请注意,使用socket.shutdownOutput()的输出socket.shutdownOutput()存在等效项

you can design a Timer class around the read() block.您可以围绕 read() 块设计一个 Timer 类。

you need to set a timeout for your timer.您需要为计时器设置超时。

on timeout just interrupt your thread.超时只是中断你的线程。

Without closing the socket, no question the best solution with the least overhead is to simply avoid using the blocking read methods until the BufferedReader is ready, or a timeout is reached.在不关闭套接字的情况下,毫无疑问,开销最小的最佳解决方案是避免使用阻塞read方法,直到BufferedReader准备好或达到超时为止。

public String readLineTimeout(BufferedReader reader, long timeout) throws TimeoutException, IOException {
    long start = System.currentTimeMillis();

    while (!reader.ready()) {
        if (System.currentTimeMillis() - start >= timeout)
            throw new TimeoutException();

        // optional delay between polling
        try { Thread.sleep(50); } catch (Exception ignore) {}
    }

    return reader.readLine(); // won't block since reader is ready
}

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 .例如,如果您想在客户端-服务器 tcp 架构中的服务器套接字上使用readLine ,则可以使用java.net.Socket setSoTimeout(int timeout)

From the Socket#setSoTimeout(int timeout) Documentation :来自Socket#setSoTimeout(int timeout) 文档

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds.使用指定的超时时间(以毫秒为单位)启用/禁用 SO_TIMEOUT。 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 .将此选项设置为非零超时后,与此 Socket 关联的 InputStream 上的 read() 调用将仅阻塞此时间量 If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid.如果超时到期,则会引发java.net.SocketTimeoutException ,尽管 Socket 仍然有效。

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).如果传入的客户端通信被接受,则从池中分配一个新线程并在ReadingThread调用 run 函数(可以调整为允许多个线程)。 On the socket used for communicating to the client the property setSoTimeout(int timeout) has been set.在用于与客户端通信的套接字上,已设置属性setSoTimeout(int timeout) Therefore if readLine does not return within the specified timeout a SocketTimeoutException is thrown.因此,如果readLine在指定的超时时间内没有返回,则会抛出 SocketTimeoutException。 You can check in a loop whether the ReadingThread has been interrupted by the main application, and if so stop reading from the socket.您可以在循环中检查ReadingThread是否已被主应用程序中断,如果是,则停止从套接字读取。

When the buffered reader is being used to read the input stream from a socket then you can achieve this by having the read call timeout.当缓冲读取器用于从套接字读取输入流时,您可以通过读取调用超时来实现这一点。 Once this timeout is triggered you will be able to check if your thread should be stopped.一旦触发此超时,您将能够检查您的线程是否应该停止。 To do this call setSoTimeout on the socket.为此,请在套接字上调用 setSoTimeout。 The read call will then have a SocketTimeoutException and you can use that to stop the thread.然后,读取调用将有一个 SocketTimeoutException,您可以使用它来停止线程。

@Override
public void run() {
    running = true;

    try {
        socket.setSoTimeout(1000); // This will determine how quick your thread responds to the shutdown call
        var inputStream = socket.getInputStream();
        bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
    } catch (IOException e) {
        Logger.error("IOException while setting up input stream");
        Logger.error(e);
        return;
    }

    StringBuilder stringBuilder = null;
    while (running) {
        try {
            int singleChar = bufferedReader.read();
            // Do something with the data
        } catch (SocketTimeoutException e) {
            // SocketTimeoutException is expected periodically as we do setSoTimeout on the socket, 
            // this makes the above read call not block for ever and allows the loop to be interrupted 
            // cleanly when we want to shut the thread down.
            Logger.trace("Socket timeout exception");
            Logger.trace(e);
        } catch (IOException e) {
            Logger.error("IOException while reading from socket stream");
            Logger.error(e);
            return;
        }
    }
}

public void stopThread() {
    running = false;
    try {
        bufferedReader.close();
    } catch (IOException e) {
        Logger.error("IOException while closing BufferedReader in SocketThread");
        Logger.error(e);
    }
}

Answer found here: Any way of using java.nio.* to interrupt a InputStream#read() without closing socket?在这里找到答案: 任何方式使用 java.nio.* 来中断 InputStream#read() 而不关闭套接字?

I think that you might have to use something other than readLine() .我认为您可能必须使用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.您可以使用read()并在每次循环迭代中检查线程是否被中断,如果中断则跳出循环。

BufferedReader reader = //...
int c;
while ((c = reader.read()) != -1){
  if (Thread.isInterrupted()){
    break;
  }
  if (c == '\n'){
    //newline
  }
  //...
}

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.一个解决方案的草图可能是这样的:NIO 提供了非阻塞 IO 的方法,所以你必须实现一个叫做Foo东西,它在套接字端使用非阻塞 NIO,但也在另一端提供InputStreamReader接口。 If the BufferedReader enters its own read , it will call Foo , which will call Selector.select with read intent.如果BufferedReader进入它自己的read ,它将调用Foo ,后者将调用Selector.select以读取意图。 select will either return indicating the presence of more data or it will block until more data is available. select要么返回表示存在更多数据,要么阻塞直到有更多数据可用。

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 .如果另一个线程想要解除对读取器的阻塞,它必须调用Selector.wakeup并且选择器可以通过BufferedReader抛出异常来优雅地返回。

The socket should be still open after that.之后套接字应该仍然打开。

Variation A: call Selector.select(timeout) to do busy polling light.变化 A:调用Selector.select(timeout)来做忙轮询灯。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM