У меня есть небольшое консольное приложение, которое я пишу, оно выполняет некоторые процессы в фоновом режиме с потоками и имеет еще один поток, который выводит результаты на консоль.
Теперь я хочу, чтобы пользователь мог вводить такие команды, как выход, и завершать программу.
У меня это работает, единственная проблема - во время цикла, если нет никаких входных данных и обработка завершена, программа ждет ввода, поэтому, пока я что-то печатаю, цикл продолжается, и условие теперь ложно, и процесс завершается.
Мой вопрос: есть ли способ сделать так, чтобы я не застревал в std::cin >> command в цикле while, чтобы он мог продолжать работать и проверять другие условия, которые могут разорвать цикл?
Я тоже пробовал создать ветку, посвященную вводу, но, похоже, она сдается, когда я жду окончания вызова inputThread.join().
Я использую переменные условия и устанавливаю atomic<bool> running = false, когда выполняются другие условия. Но я не могу заставить программу выйти, если не предоставлен никакой пользовательский ввод.
Вот скелет, чтобы попытаться продемонстрировать мою проблему.
int main(int argc, char *argv[])
{
// create thread pool doing processes, that mark as completed when done
// and dump thread that periodically works based on a run condtion
// cv.wait_for(lock, std::chrono::seconds(10), []{ return !running.load(); }
while(running)
{
if (completedThreads.load() == threadPool.size())
{
running = false;
results.clear();
cv.notify_all();
break;
}
std::cin >> command;
if (command == "dump")
{
std::cout << "Dumping Results: " << std::endl;
std::lock_guard<std::mutex> lock(mtx);
//dump results here
}
if (command == "exit")
{
running = false;
results.clear();
cv.notify_all();
break;
}
}
for(auto& thread : threadPool)
{
if (thread.joinable())
{
thread.join();
}
}
if (dumpThread.joinable())
{
dumpThread.join();
}
return 0;
}
редактировать: Если возможно, я хочу, чтобы мое решение было как можно более портативным, но если оно действительно сложное/не идеальное, я бы выбрал именно Windows. Я хотел попытаться сделать программу как можно более чистой, поэтому я не уверен, насколько плохо будет, если ОС будет использовать поток.
@user4581301 user4581301 В идеале я хотел, чтобы программа была как можно более переносимой, но на данный момент я рад сделать ее специфичной для Windows.
Примечание по использованию Stack Overflow: отвечайте на комментарии, внося изменения в вопрос, если только вы не просите разъяснений по комментарию. Вы хотите, чтобы все важные вопросы были помещены в вопрос, чтобы люди могли легко их найти. Если вы измените или расширите вопрос в комментариях, ответчики могут пропустить обновление.
Записал на будущее, обновил вопрос. Спасибо.
Вы можете вернуться к старой школьной DOS, да, той ОС, которую использовали ваши бабушка и дедушка, поддержке, которая все еще существует в Windows, а также к заголовку conio.h и опросу getch, но я бы посмотрел на ncurses. Вы получаете переносимость и можете установить тайм-аут на время ожидания ввода пользователя. По тайм-ауту вы проверяете условия выхода и либо выходите, либо возвращаетесь к ожиданию ввода. Установите тайм-аут на десятую долю секунды, и никого не будет волновать задержка при выходе, и вы не будете сжигать опросы циклов процессора.
Похоже, но, вероятно, не обман здесь: stackoverflow.com/questions/18552029/…
Да, я видел похожие темы, но тайм-аут мне здесь не поможет, я все равно так не думаю. Хотя спасибо.
Тайм-аут может дать вам точное представление о том, что вы хотите. вы читаете для ввода данных пользователем, и ничего не происходит, вы, по крайней мере, можете периодически выходить из чтения, чтобы увидеть, нужно ли вам что-то сделать, например, выйти из программы, кроме продолжения чтения. Если больше нечего делать или когда вы заканчиваете какую-либо не завершающую другую работу, вы возвращаетесь к чтению до следующего таймаута.
Более продвинутым является блокирование событий с помощью более умного инструмента асинхронного ввода-вывода, такого как select в POSIX или Overlapped IO в Windows. Никогда не пробовал с stdin, но это должно быть хотя бы несколько портативно с ASIO от Boost.
Вы настраиваете инструмент AIO для мониторинга stdin и другого источника сообщений. Когда поступают пользовательские данные, AIO просыпается и просит вас прочитать stdin. Когда вы хотите выйти, вы отправляете сообщение в альтернативный источник, и AIO просыпается и просит вас прочитать сообщение о выходе, что вам, вероятно, не нужно делать, поскольку получения сообщения о выходе достаточно, чтобы сказать вам о выходе. Если бы я знал, как правильно это сделать с помощью Boost, я бы написал ответ прямо сейчас.
Интересные предложения, спасибо, я изучаю эти методы и обновлю их, как только получу работающее решение. Насколько плохо, если я назначу обработку ввода в отдельном потоке и отключу его, чтобы программа завершилась. Нитка висит после этого?
WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE),timeout_ms) == WAIT_OBJECT_0 будет истинным, если ввод ожидает ввода на стандартном вводе. Установите время ожидания на ноль для немедленного результата. Установите значение -1 (БЕСКОНЕЧНО), чтобы дождаться, пока ввод станет доступен.
Измените приведенное выше, чтобы использовать WaitForMultipleObjects, и вы сможете отслеживать ввод пользователя И сигнал выхода. Вероятно, самое эффективное решение для Windows.





Я пытался уничтожать/уничтожать потоки с помощью cin, getchar и т. д. и всегда наблюдал случаи, когда команда неверна.
Поэтому, если можно иметь несколько exe-файлов, я предлагаю использовать выделенный процесс (и, возможно, другое консольное окно) для ввода и подход межпроцессного взаимодействия.
Вот пример (только для Windows) с использованием именованных каналов. Не до конца, но все же дает представление. IPC (имя, сокет и т. д.) существуют на всех платформах, и я почти уверен, что вы найдете переносимый класс. Возможно, система («начать ...») также должна будет зависеть от платформы.
main.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <queue>
#include <chrono>
#include <windows.h>
#define BUFSIZE 256
using std::thread;
bool running = true;
unsigned long nb_secs = 0;
std::mutex mtx;
static std::string command;
static std::queue<std::string> commands;
// simulates your calculations
void count_secs(){
while(nb_secs < 100){
std::this_thread::sleep_for(std::chrono::seconds(1));
nb_secs++;
}
}
// pipe name
LPCTSTR lpszPipename = TEXT((char*) "\\\\.\\pipe\\MethodiclesCmdPipe");
HANDLE hPipe;
void get_cmd(HANDLE cltPipe){
DWORD bytesRead;
BOOL ret_code;
std::vector<char> buffer(BUFSIZE);
do {
ret_code = ReadFile(cltPipe, buffer.data(), buffer.size(), &bytesRead, NULL);
if (!ret_code || bytesRead == 0) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
std::cout<<"**error** BROKEN_PIPE."<<std::endl;
break;
} else {
std::cout<<"**error** ReadFile failed, GLE = "<<GetLastError()<<std::endl;
break;
}
// manage errors, more data & cie
}
std::lock_guard<std::mutex> lock(mtx);
commands.push(std::string(buffer.data()));
} while (bytesRead > 0 && running);
FlushFileBuffers(cltPipe);
DisconnectNamedPipe(cltPipe);
CloseHandle(cltPipe);
running = false;// no commands => exit
}
int main(int argc, char *argv[])
{
// create an IPC - a windows namedpipe here but you may find more portable
HANDLE hPipe = CreateNamedPipe(
lpszPipename, // pipe name
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access
PIPE_TYPE_MESSAGE | // message type pipe
PIPE_READMODE_MESSAGE | // message-read mode
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // max. instances
BUFSIZE, // output buffer size
BUFSIZE, // input buffer size
0, // client time-out
NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
std::cout<<"**error** Creating named pipe. GLE = "<< GetLastError()<<std::endl;
return 1;
}
// run input process
system("start MethodiclesInput.exe");// start /B MethodiclesInput.exe to have all in the same console
std::cout<<"Waiting for client to connect..."<<std::endl;
if (ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED)) {
std::cout<<"Client connected!"<<std::endl;
} else {
std::cout<<"**error** Error connecting to client. GLE = "<< GetLastError()<<std::endl;
return 1;
}
// a simple thread that counts 1 sec loop
thread(count_secs).detach();
// the pipe (commands) reader
thread(get_cmd, hPipe).detach();
while(running)
{
// commented : not given in your example
// if (completedThreads.load() == threadPool.size())
// ...
// that part comes instead of std::cin>>
{
// every once in a while, read the commands (could also check if empty everytime, depending on what's in your loop while(running)
std::this_thread::sleep_for(std::chrono::seconds(2));
std::lock_guard<std::mutex> lock(mtx);// will use commands now
if (commands.empty())
std::cout<<'.';// purely informative...
else while(!commands.empty()){
command = commands.front();
commands.pop();
std::cout<<"Got command "<<command<<std::endl;
if (command == "dump"){
std::cout<<"Dumping Results: " << std::endl;
std::cout<<"\n val is " << nb_secs << std::endl;
}
else if (command == "exit"){
running = false;
//results.clear();
//cv.notify_all();
//break;
system("taskkill /IM MethodiclesInput.exe");
}
}
}
// ...
}
return 0;
}
inputconsole.cpp
#include <iostream>
#include <string>
#include <windows.h>
using std::cout;
using std::endl;
LPCTSTR lpszPipename = TEXT((char*) "\\\\.\\pipe\\MethodiclesCmdPipe");
int main(){
HANDLE hPipe;
hPipe = CreateFile(
lpszPipename, // pipe name
GENERIC_WRITE, // write access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, //FILE_FLAG_OVERLAPPED
NULL); // no template file
// Exit if an error other than ERROR_PIPE_BUSY occurs
int l = GetLastError();
//.. manage errors!
if (hPipe != INVALID_HANDLE_VALUE) {
do {
LPDWORD cbWritten;
//WriteFile(hPipe, LPTSTR((char*)"Hello"),12, cbWritten, NULL);
//cout<<"Sent hello : "<<cbWritten<<endl;
cout<<"Enter command."<<endl;
std::string command;
std::cin>>command;
LPTSTR lpvMessage = (char*) command.c_str();
size_t cbToWrite = (lstrlen(lpvMessage) + 1) * sizeof (TCHAR);
// sends message through the named pipe
int fSuccess = WriteFile(
hPipe, // pipe handle
lpvMessage, // message
cbToWrite, // message length
cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess) {
int l = GetLastError();
if (l) {
if (l != ERROR_PIPE_BUSY) {
cout<<"**error** Couldnt write in pipe. GLE = "<<l<<endl;
Sleep(50);
if (l == 6) {
cout<<"Closing handle "<< hPipe<<endl;
CloseHandle(hPipe);
// try to reconnect or (cleanup &) exit
getchar();
return 0;
}
} else if (!WaitNamedPipe(lpszPipename, 5000)) {
cout<<"**error** Could not open pipe: 5 second wait timed out. GLE = "<<GetLastError()<<endl;
}
// there might be other reasons / errors to manage
}
}
if (!fSuccess) {
break;
}
} while (true);
}
getchar();
return 0;
}
бегать
g++ inputconsole.cpp -o MethodiclesInput
g++ main.cpp -o test && test
Надеюсь, вам понравится, получайте удовольствие!
Очень похоже на это решение, как и на все остальные предложенные. Спасибо @Aname
iostreams намеренно слишком глупы, чтобы справиться с подобным случаем. Часто вы можете скрыть чтение в другом потоке и позволить операционной системе использовать этот поток, когда программа завершается из-за выхода из основного потока, но я предпочитаю использовать целевой (хотя обычно скрытый за переносимой библиотекой, такой как ncurses) не -блокировка ввода-вывода или ввода на основе событий. На какую ОС вы ориентируетесь и насколько переносимой должна быть программа?