除非先前的等待过期,否则确保boost :: deadline_timer不接受新的等待

时间:2015-04-01 06:46:41

标签: boost boost-asio

我正在尝试为我的C ++代码控制的硬件设备实现同步操作。

假设我可以执行两种类型的设备Open/Close。 我需要实现的是为Specified Duration打开一种类型的设备。第二类设备也是如此。

我已用boost::deadline_timer编写代码:

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>


class Test : public std::enable_shared_from_this <Test>
{
public:
    Test() :io_(), timerOne_(io_),timerTwo_(io_){}
    void Open(int num);
    void Close(int num);
    void TimedOpen(int num, int dur);
    void Run();
private:
    boost::asio::io_service io_;
    boost::asio::deadline_timer timerOne_;
    boost::asio::deadline_timer timerTwo_;
};

void Test::Open(int type)
{
    std::cout << "Open for Number : " << type << std::endl;
}

void Test::Close(int type)
{
    std::cout << "Close for Number : " << type << std::endl;
}

void Test::TimedOpen(int type, int dur)
{
    switch (type)
    {
    case 1:
    {
              io_.reset();
              auto fn = std::bind(&Test::Open, shared_from_this(), std::placeholders::_1);
              fn(type);
              timerOne_.expires_from_now(boost::posix_time::seconds(dur));
              timerOne_.async_wait(std::bind(&Test::Close, shared_from_this(), type));
              Run();
              std::cout << "Function Exiting" << std::endl;
              std::cout << "-----------------------------------------------" << std::endl;
              return;
    }

    case 2:
    {
              io_.reset();
              auto fn = std::bind(&Test::Open, shared_from_this(), std::placeholders::_1);
              fn(type);
              timerTwo_.expires_from_now(boost::posix_time::seconds(dur));
              timerTwo_.async_wait(std::bind(&Test::Close, shared_from_this(), type));
              Run();
              std::cout << "Function Exiting" << std::endl;
              std::cout << "-----------------------------------------------" << std::endl;
              return;
    }

    }

}

void Test::Run()
{
    boost::thread th(boost::bind(&boost::asio::io_service::run, &io_));
}

int main()
{
    auto t = std::make_shared<Test>();
    t->TimedOpen(1, 60);
    t->TimedOpen(2, 30);
    t->TimedOpen(1, 5);
    t->TimedOpen(2, 2);
    char line[128];
    while (std::cin.getline(line, 128))
    {
        if (strcmp(line, "\n")) break;
    }
    return 0;
}

输出是:

Open for Number : 1
Function Exiting
-----------------------------------------------
Open for Number : 2
Function Exiting
-----------------------------------------------
Open for Number : 1
Close for Number : 1
Function Exiting
-----------------------------------------------
Open for Number : 2
Close for Number : 2
Function Exiting
-----------------------------------------------
Close for Number : 2
Close for Number : 1

对于timerOne_它不会等待之前的wait过期,即只要执行t->TimedOpen(1, 5),就会取消之前的操作t->TimedOpen(1, 60)

因此,Close for Number : 1会在输出中显示,而不会等待t->TimedOpen(1, 60)

我想要实现的是,如果multiple waits are encountered适用于任何类型的timer,则所有操作都应排队,即

如果我输入:

t->TimedOpen(1, 60);
t->TimedOpen(1, 10);
t->TimedOpen(1, 5);

TimedOpen Operation秒应该60+10+5。目前它仅持续5秒。它也应该是非阻塞的,即我不能使用wait() instead of async_wait()

我如何实现它?

要点: 我的要求是在boost::deadline_timer()上安排操作,即对其进行多次操作将排队,除非先前的等待时间已过期。

1 个答案:

答案 0 :(得分:1)

就像在评论中提到的那样,您将希望每个“类型”都有队列。

让我们将每类型队列命名为“会话”。

通过链接单个strand¹上单个队列的所有异步等待,您可以获得有效的序列化(还可以避免队列/会话上的同步)。

唯一棘手的问题是在没有飞行时启动异步等待。不变量是异步操作iff !queue_.empty()

struct Session : std::enable_shared_from_this<Session> {
    Session(boost::asio::io_service &io, int type) : strand_(io), timer_(io), type(type) {}

    void Enqueue(int duration) {
        auto This = shared_from_this();
        strand_.post([This,duration,this] { 
                std::cout << "t0 + " << std::setw(4) << mark() << "ms Enqueue for Number: "  << type <<  " (dur:"  << duration       <<  ")\n";
                This->queue_.push(duration);
                if (This->queue_.size() == 1)
                    This->Wait();
            });
    }

  private:
    boost::asio::strand strand_;
    boost::asio::deadline_timer timer_;
    int type;
    std::queue<int> queue_;

    void Close() {
        assert(!queue_.empty());
        std::cout << "t0 + " << std::setw(4) << mark() << "ms Close for Number :  "  << type <<  " (dur:"  << queue_.front() <<  ") (depth " << queue_.size() << ")\n";

        queue_.pop();
        Wait();
    }
    void Wait() {
        if (!queue_.empty()) {
            std::cout << "t0 + " << std::setw(4) << mark() << "ms Open for Number :   "  << type <<  " (dur:"  << queue_.front() <<  ") (depth " << queue_.size() << ")\n";
            timer_.expires_from_now(boost::posix_time::milliseconds(queue_.front()));
            timer_.async_wait(strand_.wrap(std::bind(&Session::Close, shared_from_this())));
        }
    }
};

现在Test类变得更简单了(事实上它根本不需要“共享”,但我已经把这个细节留给了读者的谚语):

class Test : public std::enable_shared_from_this<Test> {
    using guard = boost::lock_guard<boost::mutex>;
public:
    Test() : io_(), work_(boost::asio::io_service::work(io_)) {
        io_thread = boost::thread([this] { io_.run(); });
    }

    void TimedOpen(int num, int duration);

    void Stop() {
        {
            guard lk(mx_);
            if (work_) work_.reset();
        }
        io_thread.join();
    }

    ~Test() {
        Stop();

        guard lk(mx_);
        timers_ex_.clear();
    }

private:
    mutable boost::mutex mx_;
    boost::asio::io_service io_;
    boost::optional<boost::asio::io_service::work> work_;
    std::map<int, std::shared_ptr<Session> > timers_ex_;
    boost::thread io_thread;
};

void Test::TimedOpen(int type, int duration) {
    guard lk(mx_);

    auto &session = timers_ex_[type];
    if (!session) session = std::make_shared<Session>(io_, type);

    session->Enqueue(duration);
}

你可以看到我

  • 外推到任意数量的类型
  • 使操作线程安全
  • 添加了自t0
  • 以来的相对时间戳(以毫秒为单位)
  • 修复了完全损坏的io_service生命周期。现在,施工开始了服务。 work_变量在空闲时保持活动状态。
  • Stop()关闭它(首先耗尽会话队列)。
  • 销毁调用Stop()隐式

这是一个测试运行:

<强> Live On Coliru

int main() {
    auto t = std::make_shared<Test>();
    t->TimedOpen(1, 300);
    t->TimedOpen(2, 150);
    t->TimedOpen(1,  50);
    t->TimedOpen(2,  20);

    boost::this_thread::sleep_for(boost::chrono::milliseconds(400));
    std::cout << "================\n";
    t->TimedOpen(1,  50);
    t->TimedOpen(2,  20);
    t->TimedOpen(1, 300);
    t->TimedOpen(2, 150);

    t->Stop();
}

打印

t0 +    0ms Enqueue for Number: 1 (dur:300)
t0 +    0ms Open for Number :   1 (dur:300) (depth 1)
t0 +    0ms Enqueue for Number: 2 (dur:150)
t0 +    0ms Open for Number :   2 (dur:150) (depth 1)
t0 +    0ms Enqueue for Number: 1 (dur:50)
t0 +    0ms Enqueue for Number: 2 (dur:20)
t0 +  150ms Close for Number :  2 (dur:150) (depth 2)
t0 +  150ms Open for Number :   2 (dur:20) (depth 1)
t0 +  170ms Close for Number :  2 (dur:20) (depth 1)
t0 +  300ms Close for Number :  1 (dur:300) (depth 2)
t0 +  300ms Open for Number :   1 (dur:50) (depth 1)
t0 +  350ms Close for Number :  1 (dur:50) (depth 1)
================
t0 +  400ms Enqueue for Number: 1 (dur:50)
t0 +  400ms Open for Number :   1 (dur:50) (depth 1)
t0 +  400ms Enqueue for Number: 2 (dur:20)
t0 +  400ms Open for Number :   2 (dur:20) (depth 1)
t0 +  400ms Enqueue for Number: 1 (dur:300)
t0 +  400ms Enqueue for Number: 2 (dur:150)
t0 +  420ms Close for Number :  2 (dur:20) (depth 2)
t0 +  420ms Open for Number :   2 (dur:150) (depth 1)
t0 +  450ms Close for Number :  1 (dur:50) (depth 2)
t0 +  450ms Open for Number :   1 (dur:300) (depth 1)
t0 +  570ms Close for Number :  2 (dur:150) (depth 1)
t0 +  750ms Close for Number :  1 (dur:300) (depth 1)

¹Why do I need strand per connection when using boost::asio?