boost :: asio :: async_read结束而不满足完成条件

时间:2016-04-13 12:10:09

标签: c++ boost boost-asio

在Windows上,我观察到如果async_read操作在串行端口上成功完成,我立即启动另一个async_read操作来读取n个字节,第二个{{1操作立即意外完成,成功并传输了0个字节。

  • 在第二次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

为什么会发生这种情况,我该如何避免呢?

具体来说,我在Windows上使用Boost.Asio通过ATXMEGA-192A3U模拟的RS232与微控制器通信。我向控制器发送一个启动命令,并在超时时读取输出。我通过调用下面给出代码的// 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 }); }); 函数来读取输出。该程序连续执行以下阅读任务:

  1. 将微控制器的答案检查到启动命令。此读取成功读取我期望的3个字符:ReadPort
  2. 从控制器读取R\r\n个字节的输出数百毫秒。
  3. 尽管没有读取请求的字节数,步骤2中的n操作意外地成功完成。

    async_read

    当我执行第一次按预期工作时,我收到以下输出:

    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
    };
    

    如果我在第一次完成后立即执行另一次读取,则操作在2 ms后完成,输出如下:

    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
    

    第三次阅读,紧跟在最后一次之后按预期工作:

    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
    

1 个答案:

答案 0 :(得分:1)

In short, the fundamental problem is either:

  • Asio's interpretation of the ReadFile API contract with timeouts is incorrect
  • a communication driver is violating the ReadFile API contract

The easiest solution is to account for this behavior in the application code, and issue another async_read operation if the prior one completes with success and 0 bytes read. Depending on the communication driver's implementation, a 1 milliseconds sleep between reads may work.


The COMMTIMEOUTS documentation states that for the read interval timeout:

The maximum time allowed to elapse before the arrival of the next byte on the communications line, in milliseconds. If the interval between the arrival of any two bytes exceeds this amount, the ReadFile operation is completed and any buffered data is returned. [...]

Asio's interpretation of the documentation, namely the emphasized text, is that for a given ReadFile operation, the read interval timeout begins after the first byte has been read. The implication is that if ReadFile is requested to read more than 0 bytes, Asio does not expect the ReadFile operation to return a status indicating it has successfully read 0 bytes synchronously or asynchronously. With this interpretation, Asio's implementation configures the serial port with a read interval timeout of 1 millisecond1.

// 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;

The async_read is a composed operation implemented in zero or more calls to intermediate async_read_some operations. The async_read operation interprets an intermediate async_read_some operation completing with success and 0 bytes transferred as-if no further progress will be made for the composed operation, and thus the async_read operation completes. This interpretation becomes problematic when the underlying system call to ReadFile unexpectedly completes synchronously with success and 0 bytes read.

With this details, alternative solutions are to either:

  • patch the communication driver so that a timeout interval only begins for a ReadFile operation once the operation has read at least a single byte
  • patch Asio. If one has detailed the observed behavior of ReadFile and finds that it only occurs if the ReadFile operation completes synchronously, then one may be able to patch the async_read_some() operation within win_iocp_handle_service::start_read_op. Otherwise, one could patch the various read_op specializations so that their completion predicate does not exit if 0 bytes have been read, but more than 0 had been requested.

1. If the communication driver's implementation is allowing the read interval timeout that started at the last byte read for ReadFilen operation to affect ReadFilen+1 operation that is initiated within the read interval timeout gap, then sleeping between ReadFilen and ReadFilen+1 for the period of the read interval timeout would prevent the Asio read operations from completing with success and 0 bytes when the supplied buffer's size is greater than 0 bytes.