2011-05-29 11 views
23

Estoy buscando una forma (multiplataforma) de hacer una entrada de consola sin bloqueo para mi programa C++, de modo que pueda manejar comandos de usuario mientras el programa se ejecuta continuamente. El programa también generará información al mismo tiempo.Entrada de consola sin bloqueo C++

¿Cuál es la mejor/más fácil manera de hacer esto? No tengo problemas para utilizar bibliotecas externas como boost, siempre que utilicen una licencia permisiva.

+2

Podría un hilo de trabajo ordinario de la biblioteca para usted? – Steinbitglis

+0

@Steinbitglis: ¿Qué es una biblioteca de hilos "ordinarios" y cómo se diferencia de los demás? –

+1

@Tomalak Creo que se refería a una biblioteca de hilos, una biblioteca IO no bloqueante. –

Respuesta

8

Haría esto creando un hilo separado que llamara a las funciones IO de bloqueo normales y le pasaría una función de devolución de llamada a la que llamaría cuando se ingresara. ¿Estás seguro de que necesitas hacer lo que dijiste que querías hacer?

En cuanto a la salida de información al mismo tiempo, ¿qué pasaría si el usuario estuviera escribiendo una entrada e imprimiera algo?

+1

¿Qué pasaría si tuviera un hilo esperando para decir .. cin para manejar la entrada y luego otro hilo utilizado cout para dar salida a la consola? ¿Eso terminaría mal? – Doug

+2

@Doug Realmente no debería estar produciendo e ingresando a/desde la consola desde diferentes subprocesos (a menos que desee tener algún objeto de sincronización para mantenerlos todos en línea, lo que probablemente haría que la razón por la que estaba usando subprocesos en primer lugar vaya por el desagüe). No lo he intentado antes, pero imagino que si imprime algo de un hilo mientras otro hilo está esperando la entrada y el usuario pulsa enter, el que espera la entrada obtendrá lo que el otro produjo como parte de la entrada. Entonces las cosas pueden ponerse complicadas. Como dije, no lo intenté. –

+0

@Doug: Eso estaría bien. Solo asegúrese de que ambos hilos no intenten usar la misma secuencia al mismo tiempo. – Nemo

4

He hecho esto en QNX4.5 que no admite subprocesos o Boost usando select. Básicamente, pasa select STDIN como el descriptor de archivo que se utilizará y seleccionará cuando se ingrese una nueva línea. He agregado un ciclo de ejemplo simplificado a continuación. Es independiente de la plataforma, al menos para los sistemas tipo Unix. Sin embargo, no estoy seguro acerca de Windows.

while (!g_quit) 
{ 
    //we want to receive data from stdin so add these file 
    //descriptors to the file descriptor set. These also have to be reset 
    //within the loop since select modifies the sets. 
    FD_ZERO(&read_fds); 
    FD_SET(STDIN_FILENO, &read_fds); 

    result = select(sfd + 1, &read_fds, NULL, NULL, NULL); 
    if (result == -1 && errno != EINTR) 
    { 
     cerr << "Error in select: " << strerror(errno) << "\n"; 
     break; 
    } 
    else if (result == -1 && errno == EINTR) 
    { 
     //we've received and interrupt - handle this 
     .... 
    } 
    else 
    { 
     if (FD_ISSET(STDIN_FILENO, &read_fds)) 
     { 
     process_cmd(sfd); 
     } 
    } 
} 
+4

He oído que este método no funciona en Windows. – Doug

+0

Seleccionar es mi favorito. Podría ejecutarse en cygwin o minsys lib en Windows. Debe funcionar, creo. Lo probaré y publicaré el resultado. – minghua

0

La clase StdinDataIO de la licencia BSD MUSCLE networking library soporta sin bloqueo lee de la entrada estándar en Windows, Mac OS/X, y Linux/Unix ... usted podría utilizar ese (o simplemente examinar el código como un ejemplo de cómo se puede hacer) si lo desea.

0

Puede usar la biblioteca tinycon para hacerlo. Simplemente genera un objeto tinycon en un nuevo hilo, y ya casi has terminado. Puedes definir el método de disparo para disparar lo que quieras cuando se presiona enter.

Puede encontrar aquí: https://sourceforge.net/projects/tinycon/

Además, la licencia es BSD, por lo que será la mujer más permisiva para sus necesidades.

4

Hay una manera fácil:

char buffer[512]; 
int point = 0; 
... 
while (_kbhit()) { 
    char cur = _getch(); 
    if (point > 511) point = 511; 
    std::cout << cur; 
    if (cur != 13) buffer[point++] = cur; 
    else{ 
     buffer[point] = '\0'; 
     point = 0; 
     //Run(buffer); 
    } 
} 

Sin bloque, todo en 1 hilo. En cuanto a mí, esto funciona.

+0

problema: es solo un truco, * _kbhit() * solo devuelve * verdadero * si usa su teclado de hardware. Si la entrada a su programa proviene de otro proceso, entonces * _kbhit() * bloques. – Tsoj

0

libuv es una biblioteca C multiplataforma para E/S asíncronas. Utiliza un bucle de eventos para hacer cosas como leer desde la entrada estándar sin bloquear el hilo. libuv es lo que alimenta a Node.JS y otros.

3

Entrada de consola sin bloqueo C++?

Respuestas: realice la consola IO en una secuencia de fondo y proporcione un medio de comunicación entre subprocesos.

Aquí hay un programa de prueba completo (pero simplista) que implementa async io al aplazar el io a un hilo de fondo.

el programa esperará a que ingrese cadenas (finalice con nueva línea) en la consola y luego realice una operación de 10 segundos con esa cadena.

puede ingresar otra cadena mientras la operación está en progreso.

ingrese 'salir' para que el programa se detenga en el próximo ciclo. prueba

#include <iostream> 
#include <memory> 
#include <string> 
#include <future> 
#include <thread> 
#include <mutex> 
#include <condition_variable> 
#include <deque> 

int main() 
{ 
    std::mutex m; 
    std::condition_variable cv; 
    std::string new_string; 
    bool error = false; 

    auto io_thread = std::thread([&]{ 
     std::string s; 
     while(!error && std::getline(std::cin, s, '\n')) 
     { 
      auto lock = std::unique_lock<std::mutex>(m); 
      new_string = std::move(s); 
      if (new_string == "quit") { 
       error = true; 
      } 
      lock.unlock(); 
      cv.notify_all(); 
     } 
     auto lock = std::unique_lock<std::mutex>(m); 
     error = true; 
     lock.unlock(); 
     cv.notify_all(); 
    }); 

    auto current_string = std::string(); 
    for (;;) 
    { 
     auto lock = std::unique_lock<std::mutex>(m); 
     cv.wait(lock, [&] { return error || (current_string != new_string); }); 
     if (error) 
     { 
      break; 
     } 
     current_string = new_string; 
     lock.unlock(); 

     // now use the string that arrived from our non-blocking stream 
     std::cout << "new string: " << current_string; 
     std::cout.flush(); 
     for (int i = 0 ; i < 10 ; ++i) { 
      std::this_thread::sleep_for(std::chrono::seconds(1)); 
      std::cout << " " << i; 
      std::cout.flush(); 
     } 
     std::cout << ". done. next?\n"; 
     std::cout.flush(); 
    } 
    io_thread.join(); 
    return 0; 
} 

muestra de carrera:

$ ./async.cpp 
first 
new string: first 0 1las 2t 3 
4 5 6 7 8 9. done. next? 
new string: last 0 1 2 3 4 5 6 7 8quit 9. done. next? 
+3

¿cómo le indicaría a 'io_thread' que salga sin" salir "? 'getline' está bloqueando – dashesy

+0

@dashesy Si mis requisitos fueran más complejos que esto, probablemente usaría algún código específico de la plataforma para la E/S. –

0

Ejemplo usando C++ 11:

#include <iostream> 
#include <future> 
#include <thread> 
#include <chrono> 

static std::string getAnswer() 
{  
    std::string answer; 
    std::cin >> answer; 
    return answer; 
} 

int main() 
{ 
    int timeout = 5; 
    std::cout << "do you even lift?" << std::endl; 
    std::string answer = "maybe"; //default to maybe 
    std::future<std::string> future = std::async(getAnswer); 
    if (future.wait_for(std::chrono::seconds(timeout)) == std::future_status::ready) 
     answer = future.get(); 

    std::cout << "the answer was: " << answer << std::endl; 
    exit(0); 
} 

compilador en línea: http://rextester.com/XGX58614