分析
罪魁祸首似乎是非标准
for each (auto task in tasks)
(微软扩展),基本上等同于
for (auto task : tasks)
这意味着您复制
tasks
向量,并在循环体中处理副本。
这与
PeriodicTask::execute
,特别是
timer->async_wait(boost::bind(&PeriodicTask::execute, this));
哪里
this
我们可以添加一些简单的调试跟踪,以打印向量中对象的地址以及
execute
正在调用。也在中保留一些空间
vector
,这样就不会发生重新分配来简化事情。
>example.exe
02-11-2016 20-04-36 created this=22201304
02-11-2016 20-04-36 created this=22201332
02-11-2016 20-04-36 execute this=19922484
02-11-2016 20-04-36 CPU usage
02-11-2016 20-04-36 execute this=19922484
02-11-2016 20-04-36 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
.... and so on and on and on....
让我们分析一下。让我们假设
t
指开始时间。
-
第1行:创建CPU定时器@地址
22201304
,将于
t+5秒
.
-
第2行:创建内存计时器@地址
22201332
,将于
.
-
第3、4行:将CPU定时器复制到地址
19922484
.运行处理程序。计划运行的CPU计时器
处决
关于地址处的对象
19922484
在
t+5+5秒
.
-
第5、6行:将内存计时器复制到地址
19922484
.运行处理程序。计划运行的内存计时器
处决
关于地址处的对象
19922484
在里面
t+10+10秒
.
在这个阶段,我们有两个计时器等待,一个在10秒内,另一个在启动后20秒内。它们都计划运行成员函数
处决
19922484
,此时已不存在(它是for循环中的临时项)。碰巧,内存中仍然包含来自占用该位置的最后一个对象的数据——内存任务的副本。
时间流逝。。。
-
第7行和第8行:CPU计时器触发并运行
处决
关于地址处的对象
19922484
如上所述,这意味着该方法在存储器任务的副本的上下文中运行。因此,我们看到“内存使用率”被打印出来。
此时,重新安排计时器。由于我们的上下文,我们没有重新调度CPU计时器,而是重新调度仍然挂起的内存计时器。这将导致挂起的异步等待操作被取消,这将导致调用过期处理程序并传递错误代码
boost::asio::error::operation_aborted
但是,您的到期处理程序会忽略错误代码。因此
-
第9、10行:取消触发内存定时器到期处理程序,
处决
在地址处的对象上运行
19922484
如上所述,这意味着该方法在存储器任务的副本的上下文中运行。因此,我们看到“内存使用率”被打印出来。内存计时器上已经有一个挂起的异步等待,因此我们在重新调度时会导致另一个取消。
-
第11行、第12行:取消……你明白要点了。
简单修复
将for循环更改为使用引用。
for (auto& task : tasks) {
// ....
}
控制台输出:
>so02.exe
02-11-2016 20-39-30 created this=19628176
02-11-2016 20-39-30 created this=19628204
02-11-2016 20-39-30 execute this=19628176
02-11-2016 20-39-30 CPU usage
02-11-2016 20-39-30 execute this=19628204
02-11-2016 20-39-30 Memory usage
02-11-2016 20-39-40 execute this=19628176
02-11-2016 20-39-40 CPU usage
02-11-2016 20-39-45 execute this=19628176
02-11-2016 20-39-45 CPU usage
02-11-2016 20-39-50 execute this=19628176
02-11-2016 20-39-50 CPU usage
02-11-2016 20-39-50 execute this=19628204
02-11-2016 20-39-50 Memory usage
02-11-2016 20-39-55 execute this=19628176
02-11-2016 20-39-55 CPU usage
进一步分析
我们已经修复了一个小问题,但是您所提供的代码中还有其他几个或多或少严重的问题。
一个不好的方法是初始化
std::shared_ptr<boost::asio::io_service>
地址已存在
io_service
实例(成员
PeriodicScheduler
).
该守则实质上如下:
boost::asio::io_service io_service;
std::shared_ptr<boost::asio::io_service> ptr1(&io_service);
std::shared_ptr<boost::asio::io_service> ptr2(&io_service);
这将创建该对象的3个所有者,他们彼此不了解。
班
PeriodicTask
不应该是可复制的——这没有任何意义,并且可以避免上面解决的主要问题。我的猜测是,其中的共享指针是为了解决被复制的问题
io_服务
自身不可复制)。
最后,计时器的完成处理程序应该具有
boost::system::error_code const&
参数,至少正确处理取消。
让我们从includes和一个方便的日志记录功能开始。
#include <ctime>
#include <iostream>
#include <iomanip>
#include <functional>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
void log_text(std::string const& text)
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::cout << std::put_time(&tm, "%d-%m-%Y %H-%M-%S") << " " << text << std::endl;
}
下一步,让我们
周期任务
显式不可复制,并保留对
io_服务
例子这意味着我们也可以避免其他共享指针。我们可以编写一个单独的方法
start
定时器第一次,并将其张贴在
io_服务
,因此它由
run()
最后,让我们修改完成处理程序,以处理错误状态,并在取消时正确运行。
class PeriodicTask : boost::noncopyable
{
public:
typedef std::function<void()> handler_fn;
PeriodicTask(boost::asio::io_service& ioService
, std::string const& name
, int interval
, handler_fn task)
: ioService(ioService)
, interval(interval)
, task(task)
, name(name)
, timer(ioService)
{
log_text("Create PeriodicTask '" + name + "'");
// Schedule start to be ran by the io_service
ioService.post(boost::bind(&PeriodicTask::start, this));
}
void execute(boost::system::error_code const& e)
{
if (e != boost::asio::error::operation_aborted) {
log_text("Execute PeriodicTask '" + name + "'");
task();
timer.expires_at(timer.expires_at() + boost::posix_time::seconds(interval));
start_wait();
}
}
void start()
{
log_text("Start PeriodicTask '" + name + "'");
// Uncomment if you want to call the handler on startup (i.e. at time 0)
// task();
timer.expires_from_now(boost::posix_time::seconds(interval));
start_wait();
}
private:
void start_wait()
{
timer.async_wait(boost::bind(&PeriodicTask::execute
, this
, boost::asio::placeholders::error));
}
private:
boost::asio::io_service& ioService;
boost::asio::deadline_timer timer;
handler_fn task;
std::string name;
int interval;
};
让我们拥有
周期调度器
保持向量
unique_ptr<PeriodicTask>
周期任务
现在处理启动本身,我们可以简化
run
方法最后,我们也让它不可复制,因为复制它实际上没有什么意义。
class PeriodicScheduler : boost::noncopyable
{
public:
void run()
{
io_service.run();
}
void addTask(std::string const& name
, PeriodicTask::handler_fn const& task
, int interval)
{
tasks.push_back(std::make_unique<PeriodicTask>(std::ref(io_service)
, name, interval, task));
}
private:
boost::asio::io_service io_service;
std::vector<std::unique_ptr<PeriodicTask>> tasks;
};
int main()
{
PeriodicScheduler scheduler;
scheduler.addTask("CPU", boost::bind(log_text, "* CPU USAGE"), 5);
scheduler.addTask("Memory", boost::bind(log_text, "* MEMORY USAGE"), 10);
log_text("Start io_service");
scheduler.run();
return 0;
}
控制台输出:
>example.exe
02-11-2016 19-20-42 Create PeriodicTask 'CPU'
02-11-2016 19-20-42 Create PeriodicTask 'Memory'
02-11-2016 19-20-42 Start io_service
02-11-2016 19-20-42 Start PeriodicTask 'CPU'
02-11-2016 19-20-42 Start PeriodicTask 'Memory'
02-11-2016 19-20-47 Execute PeriodicTask 'CPU'
02-11-2016 19-20-47 * CPU USAGE
02-11-2016 19-20-52 Execute PeriodicTask 'CPU'
02-11-2016 19-20-52 * CPU USAGE
02-11-2016 19-20-52 Execute PeriodicTask 'Memory'
02-11-2016 19-20-52 * MEMORY USAGE
02-11-2016 19-20-57 Execute PeriodicTask 'CPU'
02-11-2016 19-20-57 * CPU USAGE
02-11-2016 19-21-02 Execute PeriodicTask 'CPU'
02-11-2016 19-21-02 * CPU USAGE
02-11-2016 19-21-02 Execute PeriodicTask 'Memory'
02-11-2016 19-21-02 * MEMORY USAGE