2016-04-13 2 views
1

В Windows, я наблюдаю, что если async_read операция завершается успешно на последовательный порт, и я сразу же начать другую async_read операцию чтения n байт, то второй async_read операции немедленно неожиданно завершается с успехом и передается 0 байт.повышение :: ASIO :: async_read заканчивается без выполнения условия завершения

  • после второй async_read операции, если третья async_read операция инициируется для чтения n байт, то он будет в комплекте с успехом и n байт переданы

    // where buffer_size(buffer) and n are both greater than 1 
    async_read(serial, buffer, transfer_exactly(n), [](error, bytes_transferred) { 
        // completes with error=success and bytes_transferred=n 
        async_read(serial, buffer, transfer_exactly(n), [](error, bytes_transferred) { 
        // complete with error=success and bytes_transferred=0 
        async_read(serial, buffer, transfer_exactly(n), [](error, bytes_transferred) { 
         // completes with error=success and bytes_transferred=n 
        }); 
        }); 
    }); 
    
  • , если 1 миллисекунды сна выполняется между первой и второй операциями async_read, то вторая операция завершается с успехом и n переданных байт

    // where buffer_size(buffer) and n are both greater than 1 
    async_read(serial, buffer, transfer_exactly(n), [](error, bytes_transferred) { 
        // completes with error=success and bytes_transferred=n 
        sleep_for(milliseconds(1)); 
        async_read(serial, buffer, transfer_exactly(n), [](error, bytes_transferred) { 
        // completes with error=success and bytes_transferred=n 
        }); 
    }); 
    

Почему это происходит и как я могу его избежать?


Для конкретики, я использую Boost.Asio на Windows, общаться с микроконтроллером через RS232 эмулируется ATXMEGA-192A3U. Я посылаю команду запуска контроллеру и считываю вывод с таймаутом. Я прочитал вывод, вызвав функцию ReadPort, код которой приведен ниже. Программа выполняет следующие задачи чтения подряд:

  1. Проверьте ответ микроконтроллера на команду запуска. Это чтение преуспеть в чтении трех символов, которые я ожидаю: R\r\n
  2. Прочитано n байтов вывода контроллера на несколько сотен мс.

Операция async_read на шаге 2 неожиданно завершается с успехом, несмотря на то, что не прочитала количество запрошенных байтов.

class BoostBasedCommunication 
{ 
public: 
    BoostBasedCommunication(); 
    ~BoostBasedCommunication(void); 
    /*...*/ 
    virtual int ReadPort(
     int const numberOfCharacters, // maximum number of characters to be read 
     unsigned long const globalTimeout, // maximum time the operation is allowed to take in ms 
     unsigned long const intermediateTimeout, // maximum time allowed between two consequtive characters in ms 
     int& numberOfCharactersRead 
     ); 
    /*...*/ 

private: 
    /*...*/ 
    std::vector<unsigned char> inputBuffer; ///< buffer to save data to that is received 
    size_t numberOfBytesRead; ///< Number of bytes read 
    int lastErrorCode; ///< last error code 
    io_service my_io_service; ///< boost io service class 
    serial_port port; ///< boost serial port class 
    /*...*/ 
}; 

// Reads from the port until numberOfCharacters have been read, or the 
// deadline_timer has expired, or the time between two consecutive calls of 
// the completion condition is larger than intermediateTimeoutMS 

int BoostBasedCommunication::ReadPort(
    int const numberOfCharacters, // maximum number of characters to be read 
    unsigned long const globalTimeoutMS, // maximum time the operation is allowed to take in ms 
    unsigned long const intermediateTimeoutMS, // maximum time allowed between two consecutive characters in ms 
    int& numberOfCharactersRead // Actual number of characters read 
    ) 
{ 
    try 
    { 
     OutputDebugStringA("ReadPort called\r\n"); 
     my_io_service.reset(); 
     deadline_timer gloabalTimeout(my_io_service); 
     inputBuffer.resize(numberOfCharacters); 
     timeoutHandler myGlobalTimeoutHandler(&port); 

     completion_handler_2 myHandler(&gloabalTimeout, numberOfBytesRead); 
     completion_condition_2 my_completion_condition(intermediateTimeoutMS, numberOfCharacters); 

     // Set the timer 
     gloabalTimeout.expires_from_now(boost::posix_time::milliseconds(globalTimeoutMS)); 
     gloabalTimeout.async_wait(myGlobalTimeoutHandler); 

     async_read(port, boost::asio::buffer(inputBuffer, numberOfCharacters), my_completion_condition, myHandler); 

     my_io_service.run(); // run the io service 
     numberOfCharactersRead = numberOfBytesRead; 
    } 
    catch (std::exception&) 
    { 
     return COMMUNICATIONFAILED; 
    } 
    return NOERROR; 
} 

class completion_condition_2 
{ 
public: 
    completion_condition_2(
     long intermediateTimeOutTime, 
     size_t numberOfCharactersTobeRead 
     ) :intermediateTimeOutTime(intermediateTimeOutTime), 
     numberOfCharactersTobeRead(numberOfCharactersTobeRead) 
    {} 

    std::size_t operator()(
     const boost::system::error_code& error, // Result of latest async_read_some operation. 
     std::size_t bytes_transferred // Number of bytes transferred so far. 
     ) 
    { 
     if (error) 
     { 
      OutputDebugStringA(("completion_condition received error code: " + error.message() + "\r\n").c_str()); 

      if (error.value() == ERROR_OPERATION_ABORTED) 
      { 
       return 0; 
      } 
     } 

     /* ...Code concerning the intermediate timeout, which is commented out...*/ 

     if (numberOfCharactersTobeRead <= bytes_transferred) // Enough data has been read 
     { 
      std::stringstream message; 
      message << "completion_condition: bytes transferred: " << bytes_transferred << " of " << numberOfCharactersTobeRead << " => done!" << std::endl; 
      OutputDebugStringA(message.str().c_str()); 
      return 0; 
     } 
     else // More data should be read. 
     { 
      std::stringstream message; 
      message << "completion_condition: bytes transferred: " << bytes_transferred << " of " << numberOfCharactersTobeRead << " => continue!" << std::endl; 
      OutputDebugStringA(message.str().c_str()); 
      return numberOfCharactersTobeRead - bytes_transferred; 
     } 
    } 

private: 
    size_t numberOfCharactersTobeRead; ///< Number of characters to be read 
}; 

class completion_handler_2 { 
public: 
    completion_handler_2(
     deadline_timer* _globalTimeout, 
     size_t& numberOfBytesRead 
     ) :_globalTimeout(_globalTimeout), 
     numberOfBytesRead(numberOfBytesRead) 
    { 
    } 

    void operator()(
     const boost::system::error_code& error, // Result of operation. 
     std::size_t bytes_transferred   // Number of bytes read. 
     ) 
    { 
     OutputDebugStringA(("completion handler called with error code: " + error.message() + "\r\n").c_str()); 
     if (error) 
     { 
      if (error.value() == ERROR_OPERATION_ABORTED) 
      { 
       numberOfBytesRead = bytes_transferred; 
       return; 
      } 
      else 
      { 
       BOOST_THROW_EXCEPTION(std::exception("Communication failed")); 
      } 
     } 

     OutputDebugStringA("completion handler: timeout cancelation.\r\n"); 

     _globalTimeout->cancel(); 
     numberOfBytesRead = bytes_transferred; 
    } 

private: 
    deadline_timer* _globalTimeout; ///< global timeout deadline timer 
    size_t& numberOfBytesRead; ///< number of bytes read 
}; 

Когда я выполнить первое чтение, который работает, как и ожидалось, я получаю следующий вывод:

ReadPort called 
completion_condition: bytes transferred: 0 of 3 => continue! 
completion_condition: bytes transferred: 3 of 3 => done! 
completion handler called with error code: success 
completion handler timeout cancelation. 
timeoutHandler received error code: The I/O operation has been aborted because of either a thread exit or an application request 

Если я сразу же выполнить другую считаны после первого завершил, операция завершается через 2 мс со следующим выходом:

ReadPort called 
completion_condition: bytes transferred: 0 of 1024 => continue! 
completion handler called with error code: success // Why is the completion handler called here, although the completion condition did not return 0? 
completion handler timeout cancelation. 
timeoutHandler received error code: The I/O operation has been aborted because of either a thread exit or an application request 

а третьего чтения, сразу после последнего работает, как ожидалось:

ReadPort called 
completion_condition: bytes transferred: 0 of 1024 => continue! 
completion_condition: bytes transferred: 8 of 1024 => continue! 
... 
completion_condition: bytes transferred: 88 of 1024 => continue! 
completion_condition: bytes transferred: 96 of 1024 => continue! 
timeoutHandler called cancel of seriel port. 
completion_condition received error code: The I/O operation has been aborted because of either a thread exit or an application request 
completion handler called with error code: The I/O operation has been aborted because of either a thread exit or an application request 

ответ

1

Короче говоря, основная проблема в том, как:

  • интерпретация ASIO по договору ReadFile API с таймаутов неверен
  • драйвер связи нарушает контракт ReadFile API

самым простым решением является учет этого поведения в коде приложения и выдача другой операции async_read, если предыдущий завершается с успехом и 0 байтов. В зависимости от реализации драйвера связи, 1 миллисекунды сна между считанными может работы.


COMMTIMEOUTS документации говорится, что для чтения интервала тайм-аута:

Максимальное время истечь до прихода следующего байта на линии связи, в миллисекундах. Если интервал между приходом любых двух байтов превышает эту сумму, выполняется операция ReadFile и возвращаются любые буферизованные данные. [...] интерпретация

ASIO в документации, а именно подчеркнутый текст, является то, что для операции далиReadFile, прочитанный интервал ожидание начинается после того, как первого байт был считан. Импликация заключается в том, что если ReadFile запрашивается для чтения более 0 байтов, Asio не ожидает, что операция ReadFile вернет статус, указывающий, что он успешно прочитал 0 байтов синхронно или асинхронно. При такой интерпретации Asio implementation конфигурирует последовательный порт с тайм-аутом интервала считывания 1 миллисекунды .

// Set up timeouts so that the serial port will behave similarly to a 
// network socket. Reads wait for at least one byte, then return with 
// whatever they have. Writes return once everything is out the door. 
::COMMTIMEOUTS timeouts; 
timeouts.ReadIntervalTimeout = 1; 
timeouts.ReadTotalTimeoutMultiplier = 0; 
timeouts.ReadTotalTimeoutConstant = 0; 

async_read является операция сочинял реализована в ноль или более вызовов на промежуточных async_read_some операций. Операция async_read интерпретирует промежуточную операцию async_read_some, завершающую успешную работу, и 0 байт передается как-если дальнейший прогресс не будет выполнен для сложенной операции, и, таким образом, операция async_read завершается. Эта интерпретация становится проблематичной, когда базовый системный вызов ReadFile неожиданно завершается синхронно с успехом и считывается 0 байт.

С помощью этой детали, альтернативные решения, чтобы либо:

  • патч драйвер связи с тем, что тайм-аут начинается интервал только для ReadFile операции после того, как операция чтения по крайней мере один байт
  • патч ASIO , Если вы подробно описали поведение ReadFile и обнаружили, что это происходит только в том случае, если операция ReadFile завершается синхронно, тогда можно будет исправить операцию async_read_some() в пределах win_iocp_handle_service::start_read_op. В противном случае можно было бы исправить различные специализации read_op, чтобы их completion predicate не выходил, если было прочитано 0 байтов, но было запрошено более 0.

1. Если реализация коммуникационного драйвера является позволяя читать тайм-аута, который начался в последнем байте чтения для ReadFilen операции влияет ReadFilen+1 операцию, которая инициируется в промежутке интервала тайм-аута чтения, то спал между ReadFilen и ReadFilen+1 за период таймаута интервала чтения препятствовали успешному завершению операций чтения Asio и 0 байтам, если размер предоставленного буфера больше 0 байтов.

+0

Имея это в виду, я смог установить стабильную связь между компьютером и микроконтроллером. Однако, я думаю, что я не понимаю эффект тайм-аута 1 мс. Микроконтроллер в некоторых точках действует как говорящий, который выводит 8 байт примерно каждые 7,5 мс. Я могу прочитать порт на несколько 100 мс, не нажимая тайм-аут. Почему связь не прерывается после первых 8 байтов, хотя требуется больше 1 мс, пока не появятся следующие 8 байтов? –

+1

@PaulR. 'ReadFile' отключается после 8 байтов из-за его тайм-аута интервала времени ожидания 1 мс. Обратите внимание, как в вашем выводе при попытке прочитать 1024 байта с помощью операции 'async_read', каждая промежуточная операция' async_read_some' считывает 8 байтов. –

+0

ОК, я понимаю. Итак, после 8 байтов вызывается мой обратный вызов условия завершения, что я и наблюдаю. –

Смежные вопросы