如何在System.in中中断读取?

4
如果我从System.in开始阅读,它会阻塞线程直到获取数据。没有办法停止它。这是我尝试过的所有方法:
  • 中断线程
  • 停止线程
  • 关闭System.in
  • 调用System.exit(0)确实会停止线程,但也会杀死我的应用程序,所以不理想。
  • 在控制台输入字符会使方法返回,但我不能依赖用户输入。
以下是无效的示例代码:
public static void main(String[] args) throws InterruptedException {
    Thread th = new Thread(() -> {
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    th.start();
    Thread.sleep(1000);
    System.in.close();
    Thread.sleep(1000);
    th.interrupt();
    Thread.sleep(1000);
    th.stop();
    Thread.sleep(1000);
    System.out.println(th.isAlive()); // Outputs true
}

当我运行这段代码时,它会输出 true 并一直运行。 如何以可中断的方式从 System.in 读取数据?

应该是 th.close();,对吧? - Tyler Dickson
@piegames 是的,我也这么认为。你呢?结果如何? - JB Nizet
等待线程完成的正确方法是使用join()而不是Thread.sleep()。同时,调用th.stop()已经被弃用且存在潜在的安全隐患。 - Ayman
它在这里。编辑你的问题,发布你尝试过的代码和你得到的输出。 - JB Nizet
无法保证InputStream操作是否可中断。然而,Channels确实提供了这样的保证,因此包装您的InputStream可能就足够了。 - VGR
显示剩余8条评论
2个回答

4

你应该设计run方法,使其能够自行确定何时终止。调用stop()或类似的方法会固有地不安全

然而,仍然存在一个问题,即如何避免在System.in.read内部阻塞?为了做到这一点,可以在读取之前轮询System.in.available,直到它返回> 0。

示例代码:

    Thread th = new Thread(() -> {
        try {
            while(System.in.available() < 1) {
                Thread.sleep(200);
            }
            System.in.read();
        } catch (InterruptedException e) {
            // sleep interrupted
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

当然,通常认为使用阻塞IO方法比轮询更有利。但是轮询确实有其用途;在您的情况下,它允许此线程干净地退出。
更好的方法:
一种避免轮询的更好的方法是重新构造代码,使您打算终止的任何线程都无法直接访问System.in。这是因为System.in是一个不应关闭的InputStream。相反,主线程或另一个专用线程将从System.in(阻塞)读取,然后将任何内容写入缓冲区。该缓冲区反过来将由您打算终止的线程监视。
示例代码:
public static void main(String[] args) throws InterruptedException, IOException {
    PipedOutputStream stagingPipe = new PipedOutputStream();
    PipedInputStream releasingPipe = new PipedInputStream(stagingPipe);
    Thread stagingThread = new Thread(() -> {
        try {
            while(true) {
                stagingPipe.write(System.in.read());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });     
    stagingThread.setDaemon(true);
    stagingThread.start();
    Thread th = new Thread(() -> {
        try {
            releasingPipe.read();
        } catch (InterruptedIOException e) {
            // read interrupted
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    th.start();
    Thread.sleep(1000);
    Thread.sleep(1000);
    th.interrupt();
    Thread.sleep(1000);
    Thread.sleep(1000);
    System.out.println(th.isAlive()); // Outputs false
}       

但等等!(另一个Java API失败)

不幸的是,正如用户Motowski指出的那样,Java API实现中的PipedInputSteam存在一个“不会修复”的错误。因此,如果您使用上面显示的未修改的库版本的PipedInputSteam,它有时会通过wait(1000)触发长时间的休眠。为解决这个问题,开发人员必须按照此处所述创建自己的FastPipedInputStream子类。


我正在尝试使用System.in.available()创建一个包装器InputStream,但似乎无法与Scanner一起使用。 - piegames
将线程设置为守护线程,而不是使用System.exit()终止应用程序,这样做应该会产生相同的效果。 - piegames
1
PipedInputStream 是使用轮询实现的。它们使用 wait(1000) 循环。我不知道为什么,只是查看了 JDK 1.8 的源代码。 - user502187
1
@MostowskiCollapse 看起来你是正确的。又是 Java API 失败了!开发者必须自己创建 FastPipedInputStream 子类。https://dev59.com/9l4b5IYBdhLWcg3w1UzL - Patrick Parker

0
我编写了一个包装InputStream类的代码,可以被中断:
package de.piegames.voicepi.stt;
import java.io.IOException;
import java.io.InputStream;

public class InterruptibleInputStream extends InputStream {

    protected final InputStream in;

    public InterruptibleInputStream(InputStream in) {
        this.in = in;
    }

    /**
     * This will read one byte, blocking if needed. If the thread is interrupted while reading, it will stop and throw
     * an {@link IOException}.
     */     
    @Override
    public int read() throws IOException {
        while (!Thread.interrupted())
            if (in.available() > 0)
                return in.read();
            else
                Thread.yield();
        throw new IOException("Thread interrupted while reading");
    }

    /**
     * This will read multiple bytes into a buffer. While reading the first byte it will block and wait in an
     * interruptable way until one is available. For the remaining bytes, it will stop reading when none are available
     * anymore. If the thread is interrupted, it will return -1.
     */
    @Override
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int c = -1;
        while (!Thread.interrupted())
            if (in.available() > 0) {
                c = in.read();
                break;
            } else
                Thread.yield();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte) c;

        int i = 1;
        try {
            for (; i < len; i++) {
                c = -1;
                if (in.available() > 0)
                    c = in.read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte) c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

    @Override
    public int available() throws IOException {
        return in.available();
    }

    @Override
    public void close() throws IOException {
        in.close();
    }

    @Override
    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }

    @Override
    public synchronized void reset() throws IOException {
        in.reset();
    }

    @Override
    public boolean markSupported() {
        return in.markSupported();
    }
}

Thread.yield()调整为睡眠,时间长度为您可以接受的最大延迟,并准备好一些异常情况以便中断,但除此之外,它应该可以正常工作。


回答不错,但是在 read(byte b [],int off,int len) 中不应该吞咽 IOException - teppic
这可能会在EOF上旋转。InputStream中指定的available()合同是当它到达输入流的末尾时为0 - teppic
吞掉的异常代码是从InputStream.read()复制的,这是有意的。但是available()确实是一个问题,谢谢您的注意。 - piegames
@teppic 很好的发现。但在读取之前没有检测EOF的方法,我是对的吗?这似乎是另一个Java API失败。 - Patrick Parker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接