boost::asio::async_read在不满足完成条件的情况下结束

boost::asio::async_read ends without fulfilling the completion condition

本文关键字:条件 情况下 结束 async asio read boost 不满足      更新时间:2023-10-16

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

  • 在第二次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
        });
      });
    });
    
  • 如果在第一次和第二次async_read操作之间执行了1毫秒的休眠,则第二次操作将成功完成,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
      });
    });
    

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


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

  1. 检查微控制器对启动命令的响应。此读取成功读取了我期望的3个字符:Rrn
  2. 从控制器读取n字节的输出,持续几百毫秒

步骤2中的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 calledrn");
        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() + "rn").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() + "rn").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.rn");
        _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

简而言之,根本问题是:

  • Asio对ReadFile API合同超时的解释不正确
  • 通信驱动程序违反了ReadFile API合同

最简单的解决方案是在应用程序代码中说明这种行为,如果前一个操作成功并读取了0字节,则发出另一个async_read操作。根据通信驱动程序的实现,读取之间的1毫秒睡眠可能有效。


COMMTIMEOUTS文档指出,对于读取间隔超时:

下一个字节到达通信线路之前允许经过的最长时间,以毫秒为单位。如果任何两个字节到达之间的间隔超过此量,则ReadFile操作将完成,并返回任何缓冲的数据。[…]

Asio对文档(即强调文本)的解释是,对于给定的ReadFile操作,读取间隔超时在读取第一个字节后开始。这意味着,如果ReadFile被请求读取超过0个字节,Asio不希望ReadFile操作返回指示其已成功同步或异步读取0个字节的状态。通过这种解释,Asio的实现将串行端口配置为读取间隔超时为1毫秒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;

CCD_ 23是在对中间CCD_ 24操作的零个或多个调用中实现的组合操作。async_read操作解释成功完成的中间async_read_some操作和传输的0字节,就好像对所组成的操作将不进行进一步的处理一样,从而async_read操作完成。当对ReadFile的底层系统调用意外同步完成并成功读取0字节时,这种解释就成了问题。

有了这些细节,可供选择的解决方案是:

  • 修补通信驱动程序,使得只有在ReadFile操作读取了至少一个字节后,该操作才会开始超时间隔
  • 补丁Asio。如果已经详细描述了观察到的ReadFile的行为,并且发现它仅在ReadFile操作同步完成时发生,则可以在win_iocp_handle_service::start_read_op内修补async_read_some()操作。否则,可以修补各种read_op专门化,以便在读取了0个字节但请求了0个以上字节的情况下,它们的完成谓词不会退出

如果通信驱动程序的实现允许在CCD_ 35操作的最后一个字节读取时开始的读取间隔超时影响在读取间隔超时间隙内开始的CCD_,则在CCD_ 37和CCD_

相关文章: