Пользовательский ввод C++ во время поиска связанного файла

У меня есть небольшое консольное приложение, которое я пишу, оно выполняет некоторые процессы в фоновом режиме с потоками и имеет еще один поток, который выводит результаты на консоль.

Теперь я хочу, чтобы пользователь мог вводить такие команды, как выход, и завершать программу.

У меня это работает, единственная проблема - во время цикла, если нет никаких входных данных и обработка завершена, программа ждет ввода, поэтому, пока я что-то печатаю, цикл продолжается, и условие теперь ложно, и процесс завершается.

Мой вопрос: есть ли способ сделать так, чтобы я не застревал в 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. Я хотел попытаться сделать программу как можно более чистой, поэтому я не уверен, насколько плохо будет, если ОС будет использовать поток.

iostreams намеренно слишком глупы, чтобы справиться с подобным случаем. Часто вы можете скрыть чтение в другом потоке и позволить операционной системе использовать этот поток, когда программа завершается из-за выхода из основного потока, но я предпочитаю использовать целевой (хотя обычно скрытый за переносимой библиотекой, такой как ncurses) не -блокировка ввода-вывода или ввода на основе событий. На какую ОС вы ориентируетесь и насколько переносимой должна быть программа?

user4581301 21.08.2024 22:08

@user4581301 user4581301 В идеале я хотел, чтобы программа была как можно более переносимой, но на данный момент я рад сделать ее специфичной для Windows.

Methodicle 21.08.2024 22:12

Примечание по использованию Stack Overflow: отвечайте на комментарии, внося изменения в вопрос, если только вы не просите разъяснений по комментарию. Вы хотите, чтобы все важные вопросы были помещены в вопрос, чтобы люди могли легко их найти. Если вы измените или расширите вопрос в комментариях, ответчики могут пропустить обновление.

user4581301 21.08.2024 22:15

Записал на будущее, обновил вопрос. Спасибо.

Methodicle 21.08.2024 22:31

Вы можете вернуться к старой школьной DOS, да, той ОС, которую использовали ваши бабушка и дедушка, поддержке, которая все еще существует в Windows, а также к заголовку conio.h и опросу getch, но я бы посмотрел на ncurses. Вы получаете переносимость и можете установить тайм-аут на время ожидания ввода пользователя. По тайм-ауту вы проверяете условия выхода и либо выходите, либо возвращаетесь к ожиданию ввода. Установите тайм-аут на десятую долю секунды, и никого не будет волновать задержка при выходе, и вы не будете сжигать опросы циклов процессора.

user4581301 21.08.2024 23:55

Похоже, но, вероятно, не обман здесь: stackoverflow.com/questions/18552029/…

Fantastic Mr Fox 22.08.2024 01:00

Да, я видел похожие темы, но тайм-аут мне здесь не поможет, я все равно так не думаю. Хотя спасибо.

Methodicle 22.08.2024 01:24

Тайм-аут может дать вам точное представление о том, что вы хотите. вы читаете для ввода данных пользователем, и ничего не происходит, вы, по крайней мере, можете периодически выходить из чтения, чтобы увидеть, нужно ли вам что-то сделать, например, выйти из программы, кроме продолжения чтения. Если больше нечего делать или когда вы заканчиваете какую-либо не завершающую другую работу, вы возвращаетесь к чтению до следующего таймаута.

user4581301 22.08.2024 19:38

Более продвинутым является блокирование событий с помощью более умного инструмента асинхронного ввода-вывода, такого как select в POSIX или Overlapped IO в Windows. Никогда не пробовал с stdin, но это должно быть хотя бы несколько портативно с ASIO от Boost.

user4581301 22.08.2024 19:55

Вы настраиваете инструмент AIO для мониторинга stdin и другого источника сообщений. Когда поступают пользовательские данные, AIO просыпается и просит вас прочитать stdin. Когда вы хотите выйти, вы отправляете сообщение в альтернативный источник, и AIO просыпается и просит вас прочитать сообщение о выходе, что вам, вероятно, не нужно делать, поскольку получения сообщения о выходе достаточно, чтобы сказать вам о выходе. Если бы я знал, как правильно это сделать с помощью Boost, я бы написал ответ прямо сейчас.

user4581301 22.08.2024 19:56

Интересные предложения, спасибо, я изучаю эти методы и обновлю их, как только получу работающее решение. Насколько плохо, если я назначу обработку ввода в отдельном потоке и отключу его, чтобы программа завершилась. Нитка висит после этого?

Methodicle 23.08.2024 01:41
WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE),timeout_m‌​s) == WAIT_OBJECT_0 будет истинным, если ввод ожидает ввода на стандартном вводе. Установите время ожидания на ноль для немедленного результата. Установите значение -1 (БЕСКОНЕЧНО), чтобы дождаться, пока ввод станет доступен.
Dúthomhas 23.08.2024 10:59

Измените приведенное выше, чтобы использовать WaitForMultipleObjects, и вы сможете отслеживать ввод пользователя И сигнал выхода. Вероятно, самое эффективное решение для Windows.

user4581301 23.08.2024 17:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
13
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я пытался уничтожать/уничтожать потоки с помощью 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

Methodicle 24.08.2024 13:43

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