2016-02-08 2 views
1

У меня есть std::thread, который использует Повысьте-х ASIO для чтения из последовательного порта:Безопасного прерывания C++ 11 блокирует работу

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++.

+0

Довольно точно есть способ указать тайм-аут 'asio :: read_until'. Установите этот тайм-аут, возможно, 200 мс и проверьте, следует ли вы выйти, прежде чем повторять попытку. – nwp

+0

Да, это всего лишь менее агрессивные опросы, хотя ... Я бы хотел, чтобы не было опроса. – Timmmm

ответ

1

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

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

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

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

+0

этот ответ небезопасен: вызов cancel() или что-либо ASIO из обработчика сигналов - это неопределенное поведение *. Вы можете только вызывать безопасные функции обработчика сигнала, который является очень ограниченным подмножеством, большинство людей сигнализирует семафор или записывает байт в канал, чтобы разбудить поток для обработки сигнала. Я предлагаю не меняться от этой идиомы. –

+0

@NiallDouglas Нигде в моем ответе я не предлагаю обработать необработанные обработчики сигналов. Это ASIO подробно – sehe

+1

Да, вы правы. Виноват недостаток кофе перед этим ответом, извините. Я бы сказал, однако, что я не верю, что signal_set переносится, Windows, например, не использует сигналы, чтобы поймать Ctrl-C там, где вы устанавливаете консольный обработчик. –