分析
罪魁禍首似乎是在非標準for each (auto task in tasks)
(Microsoft擴展),這基本上是等價的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計時器@地址,設定在噸+5秒到期。
- 第2行:創建內存定時器@地址,設置爲在t + 10秒到期。
- 第3,4行:將CPU定時器複製到地址。處理程序。計劃的CPU計時器在地址在t + 5 + 5秒的對象上運行
execute
。
- 第5,6行:將內存定時器複製到地址。處理程序。計劃內存計時器在地址 t + 10 + 10秒上的對象上運行
execute
。
在這個階段,我們有兩個定時器掛起,一個在10秒內,一個在20秒內從啓動。他們都計劃在地址爲的對象上運行成員函數execute
,該對象在那時不再存在(它在for循環中是臨時的)。偶然的情況下,內存仍然包含佔用該位置的最後一個對象的數據 - 內存任務的副本。
時間在流逝......
- 線7,8:CPU的定時器火災,並運行
execute
的對象在地址。如上所述,這意味着該方法在Memory任務副本的上下文中運行。因此我們看到「內存使用情況」已打印。
在這一點上,計時器被重新安排。由於我們的上下文,我們不重新調度CPU定時器,而是重新調度仍在等待的內存定時器。這會導致掛起的異步等待操作被取消,這將導致調用過期處理程序並傳遞錯誤代碼boost::asio::error::operation_aborted
。但是,到期處理程序會忽略錯誤代碼。因此
簡單的解決
更改爲循環使用的參考。
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
PeriodicTask
不應該是可複製的 - 它沒有意義,並且可以避免上面解決的主要問題。我的猜測是這些共享指針是試圖解決複製的問題(而且本身不可複製)。
最後,定時器的完成處理程序應該有一個boost::system::error_code const&
參數,並且至少可以正確取消句柄。
完整的解決方案
讓我們開始與包括,和一點點便利記錄功能。
#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;
}
接下來,讓我們PeriodicTask
明確不可複製的,並且就有關io_service
實例的引用。這意味着我們可以避免其他共享指針。我們可以在第一次寫入start
的時候寫一個獨立的方法,並將其發佈到io_service
上,以便它被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;
};
讓我們PeriodicScheduler
保持unique_ptr<PeriodicTask>
的向量。由於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
一個明顯錯誤的事情是你如何初始化一個'的std :: shared_ptr的<提高:: ASIO :: io_service對象>'一個地址已經存在' io_service'實例(PeriodicScheduler的成員)。 'PeriodicTask :: ioService'應該只是一個參考。另外兩個共享指針似乎也是不必要的。另一方面,我可能會'PeriodicSheduler'保留'std :: vector>',並使'PeriodicTask'不可複製。 –