boost::asio::async_read在不满足完成条件的情况下结束
boost::asio::async_read ends without fulfilling the completion condition
在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
函数来读取输出,下面给出了该函数的代码。该程序连续执行以下读取任务:
- 检查微控制器对启动命令的响应。此读取成功读取了我期望的3个字符:
Rrn
- 从控制器读取
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_
- 在没有太多条件句的情况下,我如何避免被零除
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- Qt qml - 在没有任何条件的情况下运行一行(while(true))
- 如何在不使用宏的情况下在宏中创建条件
- 在没有互斥锁的情况下重新计数时如何避免竞争条件?
- 如何在没有'switch'和'if-else'条件的情况下重写以下代码?
- 如何编写一个通用函数,该通用函数在没有任何条件和条件的情况下工作(无论是真实和错误)
- 如何在不使用循环或迭代器的情况下检查所有列表元素并在满足条件时删除一个
- 如何在没有 c++ 中的数组/算法的情况下对两个条件的字符串进行排序
- 如何在不弄乱库 API 的情况下实现条件编译?
- 如何在没有条件变量的情况下阻止线程中的操作,并在Linux中根据信号恢复操作
- 如何在没有竞争条件的情况下将 QFutureWatcher 与 QtConcurrent::run() 一起使用
- boost::asio::async_read在不满足完成条件的情况下结束
- 如何解决,在使用元编程的条件基类的情况下没有成员函数"print"
- 在有条件的情况下使用短路运算符是否合法
- 如何在给定指向派生类型的指针的情况下有条件地将指针强制转换为基类型
- 如何在没有条件的情况下更新直方图
- 在不更改成员范围的情况下有条件地启用静态成员
- 在有条件的情况下,连接到C++和Boost Asio中的套接字
- 在不使用条件变量的情况下唤醒线程的最快方法