Java: как прервать чтение потока из System.in

У меня есть поток Java:

class MyThread extends Thread {
  @Override
  public void run() {
    BufferedReader stdin =
        new BufferedReader(new InputStreamReader(System.in));
    String msg;
    try {
      while ((msg = stdin.readLine()) != null) {
        System.out.println("Got: " + msg);
      }
      System.out.println("Aborted.");
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

}

В другой теме, как мне прервать stdin.readline() вызовите в этой теме, чтобы эта тема печатала Aborted.? я пытался System.in.close(), но это не имеет никакого значения, stdin.readline() все еще блокирует

Я заинтересован в решениях без

  • заняты ожиданием (потому что это сжигает 100% CPU);
  • спит (потому что тогда программа не мгновенно реагирует на System.in).

7 ответов

Решение

Моя первая реакция заключается в том, что нить и System.in на самом деле не ходите вместе.

Итак, сначала разделите это так, чтобы код потока не касался статического System.in,

Поток читает из InputStream и переходит в буфер. Передать InputStream в ваш существующий поток, который читает из буфера, но также проверяет, что вы не прервали.

Бюллетень Heinz Kabutz показывает, как прервать System.in читает с использованием буфера и ExecutorService,

Теперь я не знаю, протекает ли этот подход, не является переносимым или имеет неочевидные побочные эффекты. Лично я бы неохотно его использовал.

Возможно, вы сможете что-то сделать с каналами NIO и дескрипторами файлов - мои собственные эксперименты с ними не дали никаких результатов.

Как насчет...

private static BufferedReader stdInCh = new BufferedReader(
    new InputStreamReader(Channels.newInputStream((
    new FileInputStream(FileDescriptor.in)).getChannel())));

Нить где stdInch.readline() теперь вызывается, и readline() выдаст java.nio.channels.ClosedByInterruptException,

никогда не может бросить, потому что этого нет в его контракте. (Вы должны продолжить проверку перед чтением.) Фактически, этого исключения нет в контракте большинства классов и методов! (И по большей части они не утруждают себя звонками.)

Более серьезная проблема заключается в том, что (в контракте она есть) тоже не гарантирует успеха. Некоторые каналы лгут о том, что их можно прерывать, в то время как классы, использующие каналы, могут все равно заблокироваться. Например, вы можете подумать Channels.newChannel(System.in) доставит вам хороший InterruptibleChannel. Но это ложь, о которой свидетельствуют комментарии к исходному коду «Непрерывно» и «Блокировать не более одного раза» (OpenJDK 16). (Да, действительно блокирует, я проверял. Нелепо!)

Комбинация, которая, как я обнаружил, работает, - это использовать new FileInputStream(FileDescriptor.in).getChannel() с участием Scanner.

      Scanner scanner = new Scanner(new FileInputStream(FileDescriptor.in).getChannel())

while (!scanner.hasNextLine())
    Thread.sleep(100); // Internally checks Thread.interrupted() and throws InterruptedException

String line = scanner.nextLine()

Это действительно не должно быть проблемой. Несложно написать класс, проверяющий System.in.available(), Thread.interrupted(), бросает InterruptedExceptionи т. д. 🤷 Даже сканер не проверяет available если дать InputStream, или Channelв режиме блокировки (начиная с OpenJDK 16). Прокомментируйте, если вы знаете о нормальном классе, который знает.

JavaDoc для BufferedReader.readLine:

Возвращает: строка, содержащая содержимое строки, не включая символы окончания строки, или ноль, если достигнут конец потока

Исходя из этого, я не думаю, что он когда-либо вернет ноль (может ли System.in на самом деле быть закрытым, я не думаю, что он когда-либо вернет конец потока?), Поэтому цикл while не завершится. Обычный способ остановить поток - либо использовать логическую переменную в условии цикла и изменить ее извне потока, либо вызвать метод interrupt() -объекта объекта Thread (работает только в том случае, если поток является wait():ing или sleep():ing, или в методе блокировки, который выбрасывает InterruptedException). Вы также можете проверить, был ли поток прерван с помощью isInterrupted ().

Изменить: вот простая реализация с использованием isInterrupted() а также interrupt(), Основной поток ждет 5 секунд, прежде чем прерывать рабочий поток. В этом случае рабочий поток в основном занят ожиданием, так что это не так хорошо (циклически все время и проверка stdin.ready()Вы, конечно, можете оставить рабочий поток на некоторое время, если вход не готов):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;


public class MyThreadTest
{
    public static void main(String[] args)
    {
        MyThread myThread = new MyThread();
        myThread.start();

        try
        {
            Thread.sleep(5000);
        }
        catch(InterruptedException e)
        {
            //Do nothing
        }

        myThread.interrupt();

    }

    private static class MyThread extends Thread
    {       
        @Override
        public void run()
        {
            BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
            String msg;

            while(!isInterrupted())
            {
                try
                {
                    if(stdin.ready())
                    {
                        msg = stdin.readLine();
                        System.out.println("Got: " + msg);
                    }
                }
                catch(IOException e)
                {
                    e.printStackTrace();
                }
            }           
            System.out.println("Aborted.");
        }
    }

}

Кажется, нет никакого способа фактически прервать BufferedReader, если он заблокирован на readline, или, по крайней мере, я не смог его найти (используя System.in).

Как насчет определения поля в приведенном выше определении класса потока, например:

class MyThread extends Thread {   

  protected AtomicBoolean abortThread = new AtomicBoolean(false);

  public void doAbort()
  {
    this.abortThread.set(true);
  }

  @Override   public void run() 
  { 
    ...
    if (this.abortThread.get())
    {
      ...something like break loop...
    }
  }
}

Ты пытался:

Thread myThread = new MyThread();
myThread.start();
// your code.
myThread.interrupt();

Метод прерывания создает исключение InterrupedtException, и после этого вы можете обработать код.

Другие вопросы по тегам