2015-01-22 3 views
1

Что-то вроде секундомера, дайте человеку, который использует мою программу около 30 секунд, чтобы ответить, если нет ответа на выход программы? В основном ответ не должен превышать указанное время, иначе программа выйдет.Как дать пользователю определенное время для ответа?

+0

GUI или консоль? – cdhowie

+0

не gui, консоль – Lendrit

+0

это звучит как вопрос для http: //ux.stackexchange.com – DLeh

ответ

1

Если вы не хотите использовать выход и убить процесс, который вы могли бы сделать это следующим образом:

std::string getInputWithin(int timeoutInSeconds, bool *noInput = nullptr) 
{ 
    std::string answer; 

    bool exceeded = false; 
    bool gotInput = false; 

    auto r1 = std::async([&answer, &gotInput]() 
    { 
     std::getline(std::cin, answer); 
     gotInput = true; 
    }); 

    auto r2 = std::async([&timeoutInSeconds, &exceeded]() 
    { 
     std::this_thread::sleep_for(std::chrono::seconds(timeoutInSeconds)); 
     exceeded = true; 
    }); 

    while(!gotInput && !exceeded) 
    { 
     std::this_thread::sleep_for(std::chrono::milliseconds(1)); 
    } 

    if(gotInput) 
    { 
     if(noInput != nullptr) *noInput = false; 
     return answer; 
    } 

    if(noInput != nullptr) *noInput = true; 
    return ""; 
} 

int main() 
{ 
    std::cout << "please answer within 10 seconds...\n"; 

    bool noInput; 
    std::string answer = getInputWithin(10, &noInput); 

    return 0; 
} 

Хорошая вещь об этом является то, что теперь вы можете обрабатывать отсутствующий ввод, используя значение по умолчанию или просто дать пользователю второй шанс и т. д.

2

Я нашел ответ от Axalo интересным, каким бы фатальным образом не был испорчен несчастный минута std::async и std::future. Поэтому я представляю альтернативу, которая избегает std::async, но в остальном следует за базовым дизайном Axalo.

Когда я запускаю ответ Axalo на моей платформе (что соответствует соответствующим деталям), если клиент никогда не отвечает, getInputWithin никогда не возвращается и не выходит. Программа просто зависает. И если клиент хорошо отвечает за время ожидания, getInputWithin возвращается с правильным ответом, но не делает этого до истечения периода ожидания.

Причина этой проблемы тонкая. Он хорошо описан в Herb Sutter's excellent paper N3630. A ~std::future() может блокировать, если он был возвращен std::async() и будет блокироваться, пока соответствующая задача не будет выполнена. Эта функция была намеренно помещена в async/future, и в глазах некоторых, делает future совершенно бесполезным.

Axalo's r1 и r2 такие std::future s, чей деструктор должен блокировать до выполнения связанной задачи. И вот почему это решение зависает, если клиент никогда не отвечает.

Ниже приведен альтернативный вариант, который строится от thread, mutex и condition_variable. В противном случае он очень похож на ответ Axalo, но не страдает (что некоторые считают) дефектами дизайна std::async.

#include <chrono> 
#include <condition_variable> 
#include <iostream> 
#include <memory> 
#include <mutex> 
#include <stdexcept> 
#include <string> 
#include <thread> 
#include <tuple> 

std::string 
getInputWithin(std::chrono::seconds timeout) 
{ 
    auto sp = std::make_shared<std::tuple<std::mutex, std::condition_variable, 
              std::string, bool>>(); 
    std::thread([sp]() mutable 
    { 
     std::getline(std::cin, std::get<2>(*sp)); 
     std::lock_guard<std::mutex> lk(std::get<0>(*sp)); 
     std::get<3>(*sp) = true; 
     std::get<1>(*sp).notify_one(); 
     sp.reset(); 
    }).detach(); 
    std::unique_lock<std::mutex> lk(std::get<0>(*sp)); 
    if (!std::get<1>(*sp).wait_for(lk, timeout, [&]() {return std::get<3>(*sp);})) 
     throw std::runtime_error("time out"); 
    return std::get<2>(*sp); 
} 

int main() 
{ 
    std::cout << "please answer within 10 seconds...\n"; 
    std::string answer = getInputWithin(std::chrono::seconds(10)); 
    std::cout << answer << '\n'; 
} 

Примечания:

  • Время пребывания в системе chrono типа всегда. Предпочитают тип std::chrono::seconds скаляру с наводящим на размышления именем (int timeoutInSeconds против std::chrono::seconds timeout).

  • Нам необходимо запустить std::thread, чтобы обработать считывание от std::cin, как продемонстрировал Axalo. Однако нам понадобится std::mutex и std::condition_variable для связи, а не для удобства использования std::future. И основной поток, и этот вспомогательный поток должны делиться собственностью этих объектов связи, и мы не знаем, кто умрет первым. Если клиент никогда не отвечает, вспомогательный поток может жить вечно, создавая эффективную утечку памяти, что является еще одной проблемой, которая здесь не решена. Но, во всяком случае, самым простым способом совместного владения является сохранение объектов связи с помощью скопированныхstd::shared_ptr. В последнем выключен свет.

  • Запуск std::thread, который ждет std::cin и сигнализирует об основной теме, если она получена. Сигнал должен быть сделан с заблокированным mutex. Обратите внимание, что этот thread может быть (действительно должен быть) отсоединен. thread не может коснуться любой памяти, которой он не владеет (из-за shared_ptr, владеющего всей ссылочной памятью). Если main выйдет во время работы вспомогательной нити, ОС приведет к измельчению потока без использования UB.

  • Основной поток затем блокирует mutex и делает wait_for на condition_variable, используя указанный тайм-аут, и предикат, который проверяет для bool в tuple обратиться к true. Это wait_for либо вернется с этим bool, установленным на true, либо он вернется с этим значением false после timeout секунд. Если они участвуют в гонке (время ожидания и ответ клиента в то же время), то это нормально, либо там будет string, либо нет, и bool в tuple отвечает на этот вопрос. В то время как основной поток выполняет wait_for, mutex разблокирован, поэтому вспомогательная нить может его использовать.

  • Если основной поток возвращается, а bool в tuple не был установлен в true, тогда генерируется исключение. Если это исключение не будет обнаружено, будет вызван std::terminate(). В противном случае string в tuple будет иметь ответ клиента.

  • Этот подход восприимчив к клиенту, создающему множество ответов, на которые он никогда не отвечает, и тем самым эффективно увеличивая утечки памяти, удерживаемые shared_ptr, которые никогда не разрушаются. Решение этой проблемы не то, что я знаю, как сделать в переносном C++.

В C++ 14, небольшое изменение может быть сделано с getInputWithin, что уменьшает ошибку неправильного выбора члена tuple. Так как наша tuple состоит из всех различных типов, мы можем индексировать ее по типу, а не по позиции:

std::string 
getInputWithin(std::chrono::seconds timeout) 
{ 
    auto sp = std::make_shared<std::tuple<std::mutex, std::condition_variable, 
              std::string, bool>>(); 
    std::thread([sp]() mutable 
    { 
     std::getline(std::cin, std::get<std::string>(*sp)); // here 
     std::lock_guard<std::mutex> lk(std::get<std::mutex>(*sp)); // here 
     std::get<bool>(*sp) = true; // here 
     std::get<std::condition_variable>(*sp).notify_one(); // here 
     sp.reset(); 
    }).detach(); 
    std::unique_lock<std::mutex> lk(std::get<std::mutex>(*sp)); // here 
    if (!std::get<std::condition_variable>(*sp).wait_for(lk, timeout, 
              [&]() {return std::get<bool>(*sp);})) // here 
     throw std::runtime_error("time out"); 
    return std::get<std::string>(*sp); // here 
} 

То есть, линии, отмеченные // here были изменены с std::get<type>(*sp) в отличие от std::get<index>(*sp).

Update

В приступе паранойи вдохновленной хороший комментарий от TemplateRex ниже, я добавил вызов sp.reset(), как последнее, что вспомогательный поток делает. Это заставляет основной поток уничтожать tuple, исключая возможность того, что поток aux может остановиться, прежде чем уничтожить его локальную копию sp, и позволить основному удару по цепочке atexit, а затем пробудить и запустить поток aux деструктор tuple.

Могут быть другие причины, по которым необходимо позвонить sp.reset(). Но, добавив эту профилактическую медицину, нам не нужно беспокоиться об этом.

+0

Re OS убивает отдельный поток, гарантирует ли это стандарт (и где?) или это QoI от поставщиков ОС? – TemplateRex

+1

3.3.3 [basic.start.term] обсуждает эту проблему и четко заявляет, что если код обращается к стандартным библиотечным объектам (например, std :: cin) после «цепочки atexit», которые я подразумеваю для включения статических деструкторов, то вы в земле УБ. Однако, если программа заканчивается неперехваченным исключением, это вызовет 'terminate()', который по умолчанию вызывает 'abort()', который завершает программу без выполнения «цепочки atexit». Это довольно просто вытаскивает шнур питания из-под вашего приложения (включая все потоки). –

+0

Otoh, если основные выходы обычно, то по логике, созданной 'getInputWithin', поток aux не будет касаться ничего после освобождения блокировки на мьютексе, за исключением подсчетов ссылок shared_ptr, которые могут вызывать деструктор на этом кортеже. Aux-поток и основная гонка, чтобы увидеть, кто разрушает кортеж. Один победит, и один пропустит этот шаг. Если основные гонки впереди и пробиваются через цепочку atexit до того, как поток aux завершит разрушение кортежа и очистку, возможно, будет некоторый теоретический UB, но это звучит довольно далеко. –