Как будет один поток, если в System.in нет ввода в течение последних 15 секунд? - PullRequest
0 голосов
/ 17 марта 2020

Мне нужно, чтобы поток слушал system.in, объединял все входные данные и, после определенной команды или если в течение 10 секунд не было ввода, ему нужно запустить другой поток, который будет использовать собранную информацию. Чтобы было ясно, каждый раз, когда вводятся данные, мне нужно сбросить 10 секунд. Как новичок в параллельном программировании, я не совсем уверен, как подойти к этому

1 Ответ

1 голос
/ 19 марта 2020

Добро пожаловать в StackOverflow!

Простой способ добиться того, о чем вы просите, - расписание команда сканирования и ожидание возвращения соответствующего Future результат или выдать исключение во время ожидания простоя в течение некоторого времени. команда сканирования Я имею в виду Callable, который будет сканировать следующую строку из System.in.

В этом случае вы выиграли не нужно обрабатывать сложные многопоточность с помощью Thread s ручной работы. Просто создайте подходящий ExecutorService (используя соответствующий вызов метода stati c из класса Executors) для планирования команд. ExecutorService подобен планировщику Thread s, ie пулу Thread s, который обрабатывает их продолжительность жизни и отвечает, например, за их создание и запуск.

Future - это интерфейс, экземпляр которого позволяет отслеживать время выполнения задачи (например, Thread), ie проверять, завершено ли, отменять его и т. Д. c ... Callable интерфейс, реализации которого просто генерируют / возвращают результат после вычисления / вызова метода, или выдают Exception в случае, если они не могут произвести результат. Future в нашем контексте будет возвращено командами планирования ExecutorService, чтобы позволить нам отслеживать срок службы отправленных Callable s ...

Callable s, к которым мы собираемся submit просто вернет результат вызова метода Scanner.nextLine. Отправляя Callable в Scehduler, мы получаем Future, который позволяет ожидать завершения Callable в течение заданного периода времени. Чтобы бесконечно ждать завершения Callable, мы используем метод get. Чтобы ждать до определенного c тайм-аута (который мы и ищем), мы используем другой метод get, предоставляя ему количество времени, которое мы хотели бы ждать.

Существует несколько типов планировщиков (ie ExecutorService s), которые мы можем создать в Java 8 (которые я использую, как вы можете заметить по ссылкам) и выше, с помощью помощника Executors class (мы можем создать их, создавая экземпляры соответствующих классов, но для простоты мы будем использовать Executors 'stati c методы). Я не специалист по этим вопросам, но, вообще говоря, есть фиксированный пул потоков , который позволяет максимально заданному количеству Thread с запускаться в любой момент времени, есть запланированный поток пул , который может выполнять Thread с с временными ставками и периодами, существует однопотоковая их версия (ie та же концепция, только одна Thread за раз), есть это кешированный пул потоков , который создает Thread s по мере необходимости и повторно использует существующие готовые, и, наконец, есть пул рабочих краж * , в котором все его потоки блокируются / ожидают параллельно для работать (я не уверен насчет последнего, но в соответствии с документами может быть полезно, когда ваши задачи порождают другие задачи и т. д.).

Поскольку мы отправляем по одному Callable за один раз (один Scanner.nextLine вызов за раз) мы можем использовать однопотоковые версии. А поскольку нам не нужно периодически выполнять отправленные Callable, но вместо этого мы хотим отправлять их после каждого завершения, мы будем использовать фиксированный однопотоковый пул версии.

Вам также не нужно запускать другой поток, когда пользовательский ввод готов к обработке, но вы можете использовать тот же поток, который отправил Callable s. Это основной поток в следующем концептуальном коде:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Concept {

    public static void main(final String[] args) {
        final LinkedList<String> q = new LinkedList<>(); //The collection to hold all user's input.
        final Scanner scan = new Scanner(System.in); //The Scanner of the System.in input stream.
        final TimeUnit waitUnit = TimeUnit.SECONDS; //What unit of time should we use when waiting for input.
        final long waitAmount = 10; //How much time (in 'waitUnit' units) should we wait for.

        //An executor with a single (daemon) thread:
        final ExecutorService scheduler = Executors.newSingleThreadExecutor(r -> {
            final Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        });

        try {
            try {
                //Main loop for reading and waiting:
                for (String input = scheduler.submit(() -> scan.nextLine()).get(waitAmount, waitUnit);
                        !Objects.equals(input, "stop");
                        input = scheduler.submit(() -> scan.nextLine()).get(waitAmount, waitUnit))
                    q.add(input); //Add the user's last input to the collection.

                //If this is reached, then the user entered "stop" as input.
                System.out.println("Ended by user's input.");
            }
            catch (final TimeoutException tx) {
                //If this is reached, then the time timed out when waiting for user's input.
                System.out.println("Ended by timeout.");
            }
            finally {
                //Here you can "consume" however you like all the user's input from the collection:
                q.forEach(line -> System.out.println(line)); //I'm just printing all of it.
            }
        }
        catch (final InterruptedException | ExecutionException x) {
            x.printStackTrace(); //This is where you handle unexpected exceptions.
        }
        finally {
            //Whatever happened, don't forget to shutdown the ExecutorService:
            scheduler.shutdown();
        }
    }
}

Просто введите слово "стоп" в качестве ввода, и основной поток продолжит обработку ввода связанного пользователя. Или, в качестве альтернативы, вы можете подождать 10 секунд, и будет выдан TimeoutException, снова продолжая обработку ввода объединенного пользователя.

Я предоставляю вызов метода Executors 'с помощью ThreadFactory. ThreadFactory - это просто интерфейс, реализации которого создают Thread с для данных Runnable с. Runnable - это еще раз интерфейс, который на этот раз определяет единственный метод (run), который выполняет вычисления. В нашем случае это вычисление создается внутри ExecutorService для хранения ссылки на результат отправленного нами Callable, чтобы сделать его доступным для get методов возвращенного Future, что в свою очередь, сделать его доступным для кода клиента. Этот ThreadFactory, которым я поставляю ExecutorService, создает каждый Thread как демон . Daemon Thread s не останавливает завершение программы. Когда все потоки, не являющиеся daemon , завершаются, программа завершается независимо от того, работают ли еще какие-либо ( daemon ) потоки.

Таким образом, это происходит вплоть до проблемы, с которой я столкнулся при создании кода: если пользовательский ввод останавливается по таймауту вместо того, чтобы давать слово «стоп» в качестве ввода, это означает, что отправленный нами Callable еще не завершен. Отправленный нами Callable ожидает ввода от System.in. Так что поток будет работать бесконечно или пока пользователь не введет что-то. Если созданные Thread не были daemon , это не позволило бы программе завершиться. Вот почему я делаю это daemon .

Но что, если после тайм-аута вы захотите продолжить чтение из System.in с (или без) созданным объектом Scanner? Тогда вам придется сначала сохранить ссылку на последний Future, возвращаемый последним вызовом метода ExecutorService.submit.

Так вот почему у меня есть другая версия, которая полностью передает сканирование другому wrapper объект с именем TimedCallable, который вы должны использовать для каждого сканирования. Даже после истечения времени ожидания или окончания действия слова «стоп» вы должны продолжать использовать его для сканирования System.in:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Main {

    public static class TimedCallable<V> implements Callable<V> {
        private final Callable<V> callable;
        private final ExecutorService scheduler;
        private Future<V> lastFuture;

        public TimedCallable(final Callable<V> callable) {
            this.callable = Objects.requireNonNull(callable);
            scheduler = Executors.newSingleThreadExecutor(r -> {
                final Thread t = new Thread(r);
                t.setDaemon(true); //Needs to be a daemon in order to let the program end.
                return t;
            });
            lastFuture = null;
        }

        @Override
        public synchronized V call() throws InterruptedException, ExecutionException {
            if (lastFuture == null)
                try {
                    return callable.call();
                }
                catch (final Exception x) {
                    throw new ExecutionException(x);
                }
            final V v = lastFuture.get();
            lastFuture = null;
            return v;
        }

        public synchronized V call(final TimeUnit timeoutUnit,
                                   final long timeoutAmount) throws TimeoutException, InterruptedException, ExecutionException {
            if (lastFuture == null)
                lastFuture = scheduler.submit(callable);
            final V v = lastFuture.get(timeoutAmount, timeoutUnit); /*If it throws TimeoutException,
            then the 'lastFuture' property will not be nulled by the following statement:*/
            lastFuture = null;
            return v;
        }
    }

    public static void main(final String[] args) {
        final LinkedList<String> q = new LinkedList<>(); //The collection to hold all user's input.
        final Scanner scan = new Scanner(System.in); //The Scanner of the System.in input stream.
        final TimeUnit waitUnit = TimeUnit.SECONDS; //What unit of time should we use when waiting for input.
        final long waitAmount = 10; //How much time (in 'waitUnit' units) should we wait for.

        //Instantiate the scanner's timed-callable:
        final TimedCallable<String> scanNextLine = new TimedCallable<>(() -> scan.nextLine());

        try {
            try {
                //Main loop for reading and waiting:
                for (String input = scanNextLine.call(waitUnit, waitAmount); !Objects.equals(input, "stop"); input = scanNextLine.call(waitUnit, waitAmount))
                    q.add(input); //Add the user's last input to the collection.

                //If this is reached, then the user entered "stop" as input.
                System.out.println("Ended by user's input.");
            }
            catch (final TimeoutException tx) {
                //If this is reached, then the time timed out when waiting for user's input.
                System.out.println("Ended by timeout.");
            }
            finally {
                //Here you can "consume" however you like all the user's input from the collection:
                q.forEach(line -> System.out.println(line)); //I'm just printing all of it.

                //Keep on using the Scanner via the TimedCallable:
                System.out.println("Enter next line:");
                System.out.println(scanNextLine.call());
                System.out.println("Enter last line:");
                System.out.println(scanNextLine.call());
            }
        }
        catch (final InterruptedException | ExecutionException x) {
            x.printStackTrace(); //This is where you handle unexpected exceptions.
        }
    }
}

Последнее замечание: в обеих версиях я предполагал, что пользователь может быть прерван из таймаута, все еще вводя предложение. Например, если вы установите время ожидания равным 1 секунде, то у пользователя может не хватить времени, чтобы ввести то, что он хочет, до того, как истечет время ожидания и нарушит его работу. Для большего контроля над процессом ввода вам лучше создать GUI и зарегистрировать соответствующие объекты слушателя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...