【问题标题】:boost::asio::deadline_timer::async_wait not firing callbackboost::asio::deadline_timer::async_wait 不触发回调
【发布时间】:2020-09-17 18:41:21
【问题描述】:

我有一个 boost io_service 在线程中运行,我想在客户端发生特定事件 6 秒后在该线程中触发回调,如果该客户端已经在运行,则重置该客户端的计时器。

我为每个客户维护一个带有计时器的unordered_map<string, shared_ptr<deadline_timer>>

但是,在设置 async_wait 后,我的回调不会在分配的时间后触发(io_service 正在运行),当我重置指针(应该调用)时它也不会触发(带有错误代码)现有计时器的析构函数,使其发布到服务)。我该如何解决这个问题?

这是我的代码的相关部分:

auto it = timersByClientId.find(clientId);
if (it == timersByClientId.end())
{
    onLogonChangeCallback(clientId, true);
    timersByClientId[clientId].reset(
        new boost::asio::deadline_timer(replyService, boost::posix_time::seconds(6))
    );
    it = timersByClientId.find(clientId);
}
else
{
    // Cancel current wait operation (should fire the callback with an error code)
    it->second.reset(
        new boost::asio::deadline_timer(replyService, boost::posix_time::seconds(6))
    );
}
it->second->async_wait([this, clientId](const boost::system::error_code& err) {
    if (!err)
    {
        onLogonChangeCallback(clientId, false);
    }
});

如果它改变了什么,我在 Visual C++ 2010 和 boost 1.47.0 下运行。

【问题讨论】:

  • 我要检查的第一件事是验证服务是否正在运行,并且没有因任何原因过早退出。我最常看到的是队列中没有工作,导致run() 返回。从上面的 sn-p,我看不出有什么明显的错误。
  • 服务正在运行(我已经用boost::asio::io_service::work 初始化了服务),如果我从另一个来源(通过replyService.post([](){ /* something */ }); 调度),它会正常运行。

标签: c++ boost timer boost-asio


【解决方案1】:

你的代码 /looks/ok-ish。

我不确定您是如何得出结论,即当我重置指针时,您的完成处理程序不会“[...] 触发(带有错误代码)”。您忽略了这种情况(lambda 中没有 else 分支)。

把逻辑写的更清楚怎么样?

void foo(int clientId) {
    shared_timer& timer = timersByClientId[clientId];

    if (!timer) 
        onLogonChangeCallback(clientId, true);

    timer = make_timer(); // reset

    timer->async_wait([this, clientId](const boost::system::error_code& err) {
        if (!err)
            onLogonChangeCallback(clientId, false);
    });
}

这是带有 else 分支的完整演示,让您了解发生了什么。我假设有 1 个服务线程。

看到它Live On Coliru

测试负载是约 0.5 秒内 16 个帐户的 100 个会话活动。总运行时间约为 1.5 秒,因为我已将 Coliru 的会话过期时间从 6 秒减少到 1 秒。

如果您不希望 LogonManager 的析构函数等待所有会话过期,则在加入后台线程之前清除会话表:

~LogonMonitor() {
    work = boost::none;
    timersByClientId.clear();
    background.join();
}

完整列表

#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/optional.hpp>
#include <boost/make_shared.hpp>

struct LogonMonitor {
    LogonMonitor() 
        : work(io_service::work(replyService)), background([this]{ replyService.run(); })
    { }

    ~LogonMonitor() {
        work = boost::none;
        // timersByClientId.clear();
        background.join();
    }

    void foo(int clientId) {
        shared_timer& timer = timersByClientId[clientId];

        if (!timer) 
            onLogonChangeCallback(clientId, true);

        timer = make_timer(); // reset

        timer->async_wait([this, clientId](const boost::system::error_code& err) {
            if (!err)
                onLogonChangeCallback(clientId, false);
            else
                std::cout << "(cancel " << clientId << " timer)" << std::endl;
        });
    }

  private:
    using io_service   = boost::asio::io_service;
    using timer        = boost::asio::deadline_timer;
    using shared_timer = boost::shared_ptr<timer>;

    io_service replyService;
    boost::optional<io_service::work> work;
    boost::thread background;

    std::map<int, shared_timer> timersByClientId;

    shared_timer make_timer() { 
        return boost::make_shared<timer>(replyService, boost::posix_time::seconds(/*6*/1)); 
    }

    void onLogonChangeCallback(int clientId, bool newLogon) 
    {
        std::cout << __FUNCTION__ << "(" << clientId << ", " << newLogon << ")" << std::endl;
    }
};

int main()
{
    LogonMonitor instance;
    for (int i = 0; i < 100; ++i)
    {
        instance.foo(rand() % 16);
        boost::this_thread::sleep_for(boost::chrono::milliseconds(rand() % 10));
    }
}

【讨论】:

  • @RenanGemignani 现在我很好奇。答案的哪一部分有帮助?
  • 我的结论是错误的。我的问题在别处。显然,当我在 lambda 中放置断点时,VS2010 并没有中断,而且由于我使用的回调是一个虚拟的,我(错误地)认为它没有命中函数。 羞愧地垂下头我接受了你的回答,因为它说我的代码看起来不错。
  • 干杯 :) 有时每个人都会遇到这种情况。感谢您的反馈。
  • 我想知道 - 为什么work 是可选的?您确实将其设置为构造函数。因为你在析构函数中释放它?
  • @BЈовић 它是可选的,因此可以重置(更一般地说:工作的生命周期可以独立于成员进行管理)。请注意,在最新版本的 Asio 中,executor_work_guard&lt;&gt;::reset 功能是内置的,因此您不再需要 optional&lt;&gt;unique_ptr&lt;&gt;
猜你喜欢
  • 2013-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多