2016-11-02 81 views
1

我想弄清楚一個簡單的調度程序的週期性任務。這個想法是提供一種方法來計劃定期執行std::function<void()>與任何給定的時間間隔,這將是一秒的乘法。我正在嘗試使用boost :: asio編寫它,但到目前爲止,我最終會遇到一些奇怪的行爲 - 兩個計劃任務中只有一個被重複執行,但它不會遵循這個時間間隔。增強 - 週期性任務調度器

下面是代碼:

#include <functional> 
#include <iostream> 

#include <boost/asio.hpp> 
#include <boost/bind.hpp> 

class PeriodicTask 
{ 
public: 
    PeriodicTask(boost::asio::io_service * ioService, int interval, std::function<void()> task) 
    : ioService(ioService), 
     interval(interval), 
     task(std::make_shared<std::function<void()>>(task)), 
     timer(std::make_shared<boost::asio::deadline_timer>(*ioService, boost::posix_time::seconds(interval))) 
    {} 

    void execute() 
    { 
     task->operator()(); 
     timer->expires_at(timer->expires_at() + boost::posix_time::seconds(interval)); 
     timer->async_wait(boost::bind(&PeriodicTask::execute,this)); 
    } 

private: 
    std::shared_ptr<boost::asio::io_service> ioService; 
    std::shared_ptr<boost::asio::deadline_timer> timer; 
    std::shared_ptr<std::function<void()>> task; 
    int interval; 
}; 

class PeriodicScheduler 
{ 
public: 
    void run() 
    { 
     for each (auto task in tasks) 
     { 
      task.execute(); 
     } 
     io_service.run(); 
    } 
    void addTask(std::function<void()> task, int interval) 
    { 
     tasks.push_back(PeriodicTask(&io_service, interval, task)); 
    } 
    boost::asio::io_service io_service; 

private: 
    std::vector<PeriodicTask> tasks; 
}; 


void printCPUUsage() 
{ 
    std::cout << "CPU usage: " << std::endl; 
} 

void printMemoryUsage() 
{ 
    std::cout << "CPU usage: " << std::endl; 
} 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    PeriodicScheduler scheduler; 

    scheduler.addTask(printCPUUsage, 5); 
    scheduler.addTask(printMemoryUsage, 10); 

    scheduler.run(); 

    return 0; 
} 

有誰知道什麼可能導致這個問題?或者碰巧知道更好的解決問題的方法?

非常感謝!

+1

一個明顯錯誤的事情是你如何初始化一個'的std :: shared_ptr的<提高:: ASIO :: io_service對象>'一個地址已經存在' io_service'實例(PeriodicScheduler的成員)。 'PeriodicTask :: ioService'應該只是一個參考。另外兩個共享指針似乎也是不必要的。另一方面,我可能會'PeriodicSheduler'保留'std :: vector >',並使'PeriodicTask'不可複製。 –

回答

5

分析

罪魁禍首似乎是在非標準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。但是,到期處理程序會忽略錯誤代碼。因此

  • 線9,10:取消觸發存儲器計時器期滿的處理程序,在地址上對象execute運行。如上所述,這意味着該方法在Memory任務副本的上下文中運行。因此我們看到「內存使用情況」已打印。內存定時器上已經有一個待處理的異步等待,所以我們在重新調度時會導致另一個取消。

  • 第11,12行:取消...你得到了要點。

簡單的解決

更改爲循環使用的參考。

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名車主認爲對象不知道對方的。

PeriodicTaskPeriodicTask不應該是可複製的 - 它沒有意義,並且可以避免上面解決的主要問題。我的猜測是這些共享指針是試圖解決複製的問題(而且本身不可複製)。

最後,定時器的完成處理程序應該有一個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