Безопасное прерывание блокирующей операции C++11

У меня есть std::thread, который использует asio Boost для чтения из последовательного порта:

std::atomic<bool> quit(false);

void serialThread()
{
    try
    {
        asio::io_service io;
        asio::serial_port port(io);

        port.open("COM9"); // Yeay no port enumeration support!

        port.set_option(asio::serial_port_base::baud_rate(9600));

        while (!quit)
        {
            asio::streambuf buf;
            asio::read_until(port, buf, "\n");

            auto it = asio::buffers_begin(buf.data());
            string line(it, it + buf.size());

            doStuffWithLine(line);
        }
    }
    catch (std::exception e)
    {
        cout << "Serial thread error: " << e.what() << endl;
    }
}

void SetupSignals()
{
    // Arrange it so that `quit = true;` happens when Ctrl-C is pressed.
}

int main(int argc, char *argv[])
{
    SetupSignals();

    thread st(serialThread);

    st.join();

    return 0;
}

Когда я нажимаю Ctrl-C, я хочу чисто выйти из потока, чтобы все деструкторы вызывались соответствующим образом (некоторые драйверы в Windows ненавидят, если вы не закрываете их ресурсы правильно).

К сожалению, как видите, текущий код блокируется в read_until(), поэтому при нажатии Ctrl-C ничего не произойдет, пока не будет получена новая строка текста.

Одним из решений является использование опроса, что-то вроде этого:

asio::async_read_until(port, buf, "\n", ...);
while (!quit)
    io.poll();

Но я бы предпочел не использовать опрос. Это довольно неэлегантно. Единственное решение, которое я вижу в настоящее время, - это иметь std::condition_variable quitOrIoFinished, который запускается либо когда quit установлено в true, либо когда чтение заканчивается. Но я не писал asio, поэтому я не могу дать ему условную переменную для ожидания.

Есть ли какое-нибудь чистое здравое решение? В Go я бы просто использовал select для ожидания на нескольких каналах, где один из них является каналом выхода. Однако я не вижу аналогичного решения на C++.


person Timmmm    schedule 08.02.2016    source источник
comment
Почти уверен, что есть способ указать тайм-аут для asio::read_until. Установите этот тайм-аут, возможно, на 200 мс и проверьте, следует ли вам выйти, прежде чем пытаться снова.   -  person nwp    schedule 08.02.2016
comment
Да, это просто менее агрессивный опрос... Мне бы хотелось решение без опроса.   -  person Timmmm    schedule 08.02.2016


Ответы (1)


Используйте asio::signal_set для ожидания сигнала INT (control-C стремится отправить прерывание).

Когда он прибудет, просто вызовите cancel() для ваших объектов ввода-вывода с ожидающими асинхронными операциями. Они вернутся с error_code равным boost::asio::error::operation_aborted.

Теперь, если у вас есть объект io_service::work, уничтожьте его, и все потоки, выполняющие io_service::run(), вернутся, чтобы вы могли присоединиться к ним.

Примечание Позаботьтесь о синхронизации доступа к вашим объектам ввода-вывода (например, когда вы вызываете для них cancel()), поскольку эти объекты не являются потокобезопасными, в отличие от io_service и strand.

person sehe    schedule 08.02.2016
comment
этот ответ небезопасен: вызов cancel() или любого другого ASIO из обработчика сигнала является неопределенным поведением. Вы можете вызывать только безопасные функции обработчика сигналов, что является очень ограниченным подмножеством, большинство людей сигнализируют семафор или записывают байт в канал, чтобы разбудить поток для обработки сигнала. Я бы предложил не отходить от этой идиомы. - person Niall Douglas; 09.02.2016
comment
@NiallDouglas Нигде в моем ответе я не предлагал иметь дело с обработчиками необработанных сигналов. Это деталь ASIO - person sehe; 09.02.2016
comment
Да, ты прав. Вините отсутствие кофе перед этим ответом, извините. Я хотел бы упомянуть, однако, что я не верю, что signal_set является переносимым, например, Windows не использует сигналы, чтобы поймать Ctrl-C, вы устанавливаете обработчик управления консолью. - person Niall Douglas; 09.02.2016