Я удивлен, узнав, что по прошествии 5 лет все ответы по-прежнему страдают одной или несколькими из следующих проблем:
- Используется функция, отличная от ReadLine, что приводит к потере функциональности. (Удалить / возврат / клавишу вверх для предыдущего ввода).
- Функция плохо себя ведет при многократном вызове (порождение нескольких потоков, зависание многих строк ReadLine или иное неожиданное поведение).
- Функция полагается на ожидание занятости. Это ужасная трата, поскольку ожидается, что ожидание будет длиться от нескольких секунд до тайм-аута, который может составлять несколько минут. Ожидание с занятостью, которое выполняется в течение такого количества времени, представляет собой ужасную нехватку ресурсов, что особенно плохо в сценарии многопоточности. Если busy-wait модифицируется с помощью сна, это отрицательно сказывается на скорости отклика, хотя я признаю, что это, вероятно, не является большой проблемой.
Я считаю, что мое решение решит исходную проблему без каких-либо из перечисленных выше проблем:
class Reader {
private static Thread inputThread;
private static AutoResetEvent getInput, gotInput;
private static string input;
static Reader() {
getInput = new AutoResetEvent(false);
gotInput = new AutoResetEvent(false);
inputThread = new Thread(reader);
inputThread.IsBackground = true;
inputThread.Start();
}
private static void reader() {
while (true) {
getInput.WaitOne();
input = Console.ReadLine();
gotInput.Set();
}
}
// omit the parameter to read a line without a timeout
public static string ReadLine(int timeOutMillisecs = Timeout.Infinite) {
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
return input;
else
throw new TimeoutException("User did not provide input within the timelimit.");
}
}
Позвонить, конечно же, очень просто:
try {
Console.WriteLine("Please enter your name within the next 5 seconds.");
string name = Reader.ReadLine(5000);
Console.WriteLine("Hello, {0}!", name);
} catch (TimeoutException) {
Console.WriteLine("Sorry, you waited too long.");
}
В качестве альтернативы вы можете использовать TryXX(out)
соглашение, как предложил Шмуэли:
public static bool TryReadLine(out string line, int timeOutMillisecs = Timeout.Infinite) {
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
line = input;
else
line = null;
return success;
}
Что называется следующим образом:
Console.WriteLine("Please enter your name within the next 5 seconds.");
string name;
bool success = Reader.TryReadLine(out name, 5000);
if (!success)
Console.WriteLine("Sorry, you waited too long.");
else
Console.WriteLine("Hello, {0}!", name);
В обоих случаях нельзя смешивать вызовы Reader
с обычными Console.ReadLine
вызовами: если Reader
время истекло, будет зависший ReadLine
вызов. Вместо этого, если вы хотите иметь обычный (не рассчитанный по времени) ReadLine
вызов, просто используйте Reader
и опустите тайм-аут, чтобы по умолчанию он был бесконечным.
Так как насчет проблем, связанных с другими упомянутыми мною решениями?
- Как видите, используется ReadLine, что позволяет избежать первой проблемы.
- Функция ведет себя правильно при многократном вызове. Независимо от того, наступит ли тайм-аут или нет, будет запущен только один фоновый поток и будет активным не более одного вызова ReadLine. Вызов функции всегда будет приводить к последнему вводу или к тайм-ауту, и пользователю не нужно будет нажимать ввод более одного раза, чтобы отправить свой ввод.
- И, очевидно, функция не полагается на ожидание занятости. Вместо этого он использует правильные методы многопоточности для предотвращения потери ресурсов.
Единственная проблема, которую я предвижу в этом решении, заключается в том, что оно не является потокобезопасным. Однако несколько потоков действительно не могут запрашивать у пользователя ввод одновременно, поэтому синхронизация должна происходить до того, как в Reader.ReadLine
любом случае будет выполняться вызов .
horrible waste
, но, конечно, ваша сигнализация лучше. Кроме того, использование одного блокирующегоConsole.ReadLine
вызова в бесконечном цикле во второй угрозе предотвращает проблемы, когда множество таких вызовов остается в фоновом режиме, как и в других решениях, получивших большое количество голосов ниже. Спасибо, что поделились своим кодом. +1Console.ReadLine()
вызове, который вы делаете. В итоге вы получаете «фантом»,ReadLine
который нужно выполнить в первую очередь.getInput
.источник
ReadLine
кого вы звоните, сидит и ждет ввода. Если вы вызовете его 100 раз, он создаст 100 потоков, которые не исчезнут, пока вы не нажмете Enter 100 раз!Поможет ли этот подход с использованием Console.KeyAvailable ?
источник
KeyAvailable
указывает только на то, что пользователь начал вводить данные в ReadLine, но нам нужно событие при нажатии Enter, которое заставит ReadLine вернуться. Это решение работает только для ReadKey, т.е. получение только одного символа. Поскольку это не решает фактический вопрос для ReadLine, я не могу использовать ваше решение. -1 извинитеЭто сработало для меня.
источник
Так или иначе, вам нужен второй поток. Вы можете использовать асинхронный ввод-вывод, чтобы не объявлять свой собственный:
Если чтение возвращает данные, установите событие, и ваш основной поток будет продолжен, в противном случае вы продолжите работу по истечении тайм-аута.
источник
источник
Я думаю, вам нужно будет создать вторичный поток и опросить ключ на консоли. Я не знаю встроенного способа сделать это.
источник
Я боролся с этой проблемой 5 месяцев, прежде чем нашел решение, которое отлично работает в корпоративной среде.
Проблема с большинством решений до сих пор заключается в том, что они полагаются на что-то другое, кроме Console.ReadLine (), а Console.ReadLine () имеет множество преимуществ:
Мое решение таково:
Образец кода:
Дополнительная информация об этом методе, включая правильный метод прерывания потока, использующего Console.ReadLine:
Вызов .NET для отправки нажатия клавиши [ввод] в текущий процесс, который является консольным приложением?
Как прервать другой поток в .NET, когда указанный поток выполняет Console.ReadLine?
источник
Вызов Console.ReadLine () в делегате - это плохо, потому что, если пользователь не нажмет «ввод», этот вызов никогда не вернется. Поток, выполняющий делегата, будет заблокирован до тех пор, пока пользователь не нажмет «Enter», без возможности отменить его.
Последовательность этих вызовов не приведет к ожидаемому результату. Рассмотрим следующее (используя приведенный выше пример класса Console):
Пользователь позволяет истечь таймауту для первого приглашения, а затем вводит значение для второго приглашения. И firstName, и lastName будут содержать значения по умолчанию. Когда пользователь нажимает «ввод», первый вызов ReadLine завершается, но код отказался от этого вызова и, по существу, отбросил результат. второй ReadLine вызов будет продолжать блокировать, тайм - аут, в конечном счете истекает , и возвращаемое значение снова будет по умолчанию.
Кстати, в приведенном выше коде есть ошибка. Вызывая waitHandle.Close (), вы закрываете событие из-под рабочего потока. Если пользователь нажимает «ввод» после истечения тайм-аута, рабочий поток попытается сигнализировать о событии, которое вызывает исключение ObjectDisposedException. Исключение генерируется из рабочего потока, и если вы не настроили обработчик необработанных исключений, ваш процесс завершится.
источник
Если вы используете
Main()
метод, вы не можете его использоватьawait
, поэтому вам придется использоватьTask.WaitAny()
:Однако в C # 7.1 появилась возможность создавать асинхронные
Main()
метода, поэтому лучше использовать этуTask.WhenAny()
версию всякий раз, когда у вас есть такая возможность:источник
Возможно, я слишком много вчитываюсь в вопрос, но я предполагаю, что ожидание будет похоже на меню загрузки, где оно ждет 15 секунд, если вы не нажмете клавишу. Вы можете использовать (1) функцию блокировки или (2) вы можете использовать поток, событие и таймер. Событие будет действовать как «продолжение» и блокироваться до истечения времени таймера или нажатия клавиши.
Псевдокод для (1) будет:
источник
К сожалению, я не могу комментировать пост Гульзар, но вот более полный пример:
источник
РЕДАКТИРОВАТЬ : исправлена проблема, выполняя фактическую работу в отдельном процессе и убивая этот процесс, если он истекает. Подробнее см. Ниже. Уф!
Просто попробовал, и, похоже, он работал хорошо. У моего коллеги была версия, в которой использовался объект Thread, но я считаю метод BeginInvoke () для типов делегатов более элегантным.
Проект ReadLine.exe очень простой, у него есть один класс, который выглядит так:
источник
Console.ReadLine()
будут блокирование и задержать ввод в следующем запросе. Принятый ответ довольно близок, но все же имеет ограничения.ReadLine()
в свою программу еще одну после вызова этой. Посмотри, что получится. Вы должны нажать return ДВАЖДЫ, чтобы заставить его работать из-за однопоточной природыConsole
. Это. Не имеет . Работай..NET 4 делает это невероятно простым с помощью задач.
Сначала создайте своего помощника:
Во-вторых, выполните задачу и ждите:
Не нужно пытаться воссоздать функциональность ReadLine или выполнять другие опасные взломы, чтобы заставить это работать. Задачи позволяют решить вопрос самым естественным образом.
источник
Как будто здесь уже не было достаточно ответов: 0), следующее инкапсулируется в решение статического метода @kwl выше (первое).
использование
источник
Простой пример многопоточности для решения этой проблемы
или статическая строка вверху для получения всей строки.
источник
В моем случае это работает нормально:
источник
Разве это не мило и коротко?
источник
Это более полный пример решения Глена Слейдена. Мне случилось сделать это при создании тестового примера для другой проблемы. Он использует асинхронный ввод-вывод и событие ручного сброса.
источник
Мой код полностью основан на ответе друга @JSQuareD
Но мне нужно было использовать
Stopwatch
таймер, потому что, когда я закончил программу,Console.ReadKey()
он все еще ждалConsole.ReadLine()
и генерировал неожиданное поведение.Для меня это СРАБОТАЛО ОТЛИЧНО. Сохраняет исходную Console.ReadLine ()
источник
Еще один дешевый способ получить второй поток - обернуть его делегатом.
источник
Пример реализации поста Эрика выше. Этот конкретный пример использовался для чтения информации, которая была передана в консольное приложение через конвейер:
источник
Обратите внимание, что если вы пойдете по маршруту Console.ReadKey, вы потеряете некоторые интересные функции ReadLine, а именно:
Чтобы добавить тайм-аут, измените цикл while по своему усмотрению.
источник
Пожалуйста, не ненавидьте меня за то, что я добавил еще одно решение к множеству существующих ответов! Это работает для Console.ReadKey (), но может быть легко изменено для работы с ReadLine () и т. Д.
Поскольку методы "Console.Read" блокируют, необходимо " подтолкнуть » поток StdIn, чтобы отменить чтение.
Синтаксис вызова:
Код:
источник
Вот решение, которое использует
Console.KeyAvailable
. Это блокирующие вызовы, но при желании их асинхронно вызывать через TPL должно быть довольно просто. Я использовал стандартные механизмы отмены, чтобы упростить подключение к асинхронному шаблону задач и всему тому хорошему.У этого есть некоторые недостатки.
ReadLine
(прокрутка вверх / вниз и т. Д.).источник
Заканчивается здесь, потому что был задан повторяющийся вопрос. Я придумал следующее решение, которое выглядит простым. Я уверен, что у него есть некоторые недостатки, которые я упустил.
источник
Я пришел к этому ответу и в итоге сделал:
источник
Простой пример с использованием
Console.KeyAvailable
:источник
Более современный код, основанный на задачах, будет выглядеть примерно так:
источник
У меня была уникальная ситуация с приложением Windows (службой Windows). При запуске программы в интерактивном режиме
Environment.IsInteractive
(VS Debugger или из cmd.exe) я использовал AttachConsole / AllocConsole для получения моего stdin / stdout. Чтобы не допустить завершения процесса во время выполнения работы, поток пользовательского интерфейса вызываетConsole.ReadKey(false)
. Я хотел отменить ожидание, которое поток пользовательского интерфейса выполнял из другого потока, поэтому я придумал модификацию решения с помощью @JSquaredD.источник