将对象的所有异步操作正确地放入一个`boost::asio::strand`中的方法。

huangapple go评论88阅读模式
英文:

Correct way to put all asynchronous operations of an object into a boost::asio::strand

问题

The official Boost.Asio tutorial <https://www.boost.org/doc/libs/1_81_0/doc/html/boost_asio/tutorial/tuttimer5.html> demonstrates how one can use the boost::asio::bind_executor function that returns a new handler that automatically dispatches its contained completion handler through the strand object:

#include &lt;boost/asio.hpp&gt;
#include &lt;iostream&gt;
#include &lt;thread&gt;

class Printer1
{
public:
Printer1(boost::asio::io_context&amp; io)
: m_strand{boost::asio::make_strand(io)}
, m_timer1{io}
, m_timer2{io}
{
    m_timer1.async_wait(boost::asio::bind_executor(m_strand, [this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;1 &quot;;}));
    m_timer2.async_wait(boost::asio::bind_executor(m_strand, [this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;2 &quot;;}));
}
private:
    boost::asio::strand&lt;boost::asio::io_context::executor_type&gt; m_strand;
    boost::asio::steady_timer m_timer1;
    boost::asio::steady_timer m_timer2;
};

Now lets assume that one wants to put all operations of the timers into the same strand.

Would

class Printer2
{
public:
Printer2(boost::asio::io_context&amp; io)
: m_strand{boost::asio::make_strand(io)}
, m_timer1{m_strand}
, m_timer2{m_strand}
{
    m_timer1.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;1 &quot;;});
    m_timer2.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;2 &quot;;});
}
private:
    boost::asio::strand&lt;boost::asio::io_context::executor_type&gt; m_strand;
    boost::asio::steady_timer m_timer1;
    boost::asio::steady_timer m_timer2;
};

or

class Printer3
{
public:
template &lt;typename Executor&gt;
Printer3(Executor executor)
: m_timer1{executor}
, m_timer2{executor}
{
    m_timer1.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;1 &quot;;});
    m_timer2.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;2 &quot;;});
}
private:
    boost::asio::steady_timer m_timer1;
    boost::asio::steady_timer m_timer2;
};

int main()
{
    boost::asio::io_context io;
    Printer3 p{boost::asio::make_strand(io)};
    // ...
}

do the job?

英文:

The official Boost.Asio tutorial <https://www.boost.org/doc/libs/1_81_0/doc/html/boost_asio/tutorial/tuttimer5.html> demonstrates how one can use the boost::asio::bind_executor function that returns a new handler that automatically dispatches its contained completion handler through the strand object:

#include &lt;boost/asio.hpp&gt;
#include &lt;iostream&gt;
#include &lt;thread&gt;

class Printer1
{
public:
Printer1(boost::asio::io_context&amp; io)
: m_strand{boost::asio::make_strand(io)}
, m_timer1{io}
, m_timer2{io}
{
    m_timer1.async_wait(boost::asio::bind_executor(m_strand, [this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;1 &quot;;}));
    m_timer2.async_wait(boost::asio::bind_executor(m_strand, [this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;2 &quot;;}));
}
private:
    boost::asio::strand&lt;boost::asio::io_context::executor_type&gt; m_strand;
    boost::asio::steady_timer m_timer1;
    boost::asio::steady_timer m_timer2;
};

Now lets assume that one wants to put all operations of the timers into the same strand.

Would

class Printer2
{
public:
Printer2(boost::asio::io_context&amp; io)
: m_strand{boost::asio::make_strand(io)}
, m_timer1{m_strand}
, m_timer2{m_strand}
{
    m_timer1.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;1 &quot;;});
    m_timer1.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;2 &quot;;});
}
private:
    boost::asio::strand&lt;boost::asio::io_context::executor_type&gt; m_strand;
    boost::asio::steady_timer m_timer1;
    boost::asio::steady_timer m_timer2;
};

or

class Printer3
{
public:
template &lt;typename Executor&gt;
Printer3(Executor executor)
: m_timer1{executor}
, m_timer2{executor}
{
    m_timer1.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;1 &quot;;});
    m_timer1.async_wait([this](const boost::system::error_code&amp; error){std::cout &lt;&lt; &quot;2 &quot;;});
}
private:
    boost::asio::steady_timer m_timer1;
    boost::asio::steady_timer m_timer2;
};

int main()
{
    boost::asio::io_context io;
    Printer3 p{boost::asio::make_strand(io)};
    // ...
}

do the job?

答案1

得分: 3

one can use the boost::asio::bind_executor function that returns a new handler that automatically dispatches its contained completion handler through the strand object

不会创建一个“自动调度”的处理程序。相反,它会使用信息装饰处理程序,以允许库代码正确调度到关联的执行器。

Q. would Printer2 or Printer3 do the job?

是的,两者都可以,因为async_wait操作会在IO对象的默认执行器上调用处理程序,除非处理程序已经关联到不同的执行器。

Simplified all of the examples:

所有示例都被简化了:

Live On Coliru

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <thread>

namespace asio = boost::asio;
using namespace std::chrono_literals;

static inline auto handler(int id) {
static auto constexpr now = std::chrono::steady_clock::now;
static auto const start = now();
return [=](boost::system::error_code ec) {
std::cout //
<< std::setw(6) << (now() - start) / 1ms << "ms " //
<< id << " (" << ec.message() << ")" //
<< std::endl;
};
}

struct Test1 {
Test1(asio::io_context& io) : m_ex{make_strand(io)} {
t1.async_wait(bind_executor(m_ex, handler(1)));
t2.async_wait(bind_executor(m_ex, handler(2)));
}

private:
asio::any_io_executor m_ex;
asio::steady_timer t1{m_ex, 100ms}, t2{m_ex, 200ms};
};

struct Test2 {
Test2(asio::io_context& io) : m_ex{make_strand(io)} {
t1.async_wait(handler(3));
t2.async_wait(handler(4));
}

private:
asio::any_io_executor m_ex;
asio::steady_timer t1{m_ex, 300ms}, t2{m_ex, 400ms};
};

struct Test3 {
template <typename Ex> Test3(Ex ex) : t1{ex, 500ms}, t2{ex, 600ms} {
t1.async_wait(handler(5));
t2.async_wait(handler(6));
}

private:
asio::steady_timer t1, t2;
};

int main() {
asio::io_context ioc;

Test1 t1{ioc};
Test2 t2{ioc};
Test3 t3{make_strand(ioc)};

ioc.run();

}

Prints

100ms 1 (Success)
200ms 2 (Success)
300ms 3 (Success)
400ms 4 (Success)
500ms 5 (Success)
600ms 6 (Success)

The third one has a distinct benefit of not hardcoding the executor type, which is beneficial in case you... use a different kind of context. Like, for example one that uses a thread pool.

第三个示例的明显优点是不会硬编码执行器类型,这在使用不同类型的上下文时非常有用,比如使用线程池的上下文。

To avoid templating the constructor, consider using the default type-erased executor type used by all IO objects:

为避免为构造函数添加模板,可以考虑使用所有IO对象使用的默认类型擦除执行器类型:

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;

struct Ultimate {
Ultimate(asio::any_io_executor ex) : t1{ex, 1s}, t2{ex, 2s} {
std::cout << std::unitbuf;
t1.async_wait( { std::cout << "1 "; });
t2.async_wait( { std::cout << "2 "; });
}

private:
asio::steady_timer t1, t2;
};

int main() {
asio::thread_pool ioc;

Ultimate t3{make_strand(ioc)};

ioc.join();

}

将对象的所有异步操作正确地放入一个`boost::asio::strand`中的方法。

英文:

> one can use the boost::asio::bind_executor function that returns a new handler that automatically dispatches its contained completion handler through the strand object

It doesn't create a handler that "automatically dispatches". Instead, it decorates the handler with information that allows the library code to correctly dispatch to the associated executor.

> Q. would Printer2 or Printer3 do the job?

Yes. Both do the job, because the async_wait operation invokes the handler on the default executor of the IO object unless the handler has been associated with a different one.

Simplified all of the examples:

Live On Coliru

#include &lt;boost/asio.hpp&gt;
#include &lt;iomanip&gt;
#include &lt;iostream&gt;
#include &lt;thread&gt;

namespace asio = boost::asio;
using namespace std::chrono_literals;

static inline auto handler(int id) {
    static auto constexpr now = std::chrono::steady_clock::now;
    static auto const start   = now();
    return [=](boost::system::error_code ec) {
        std::cout                                             //
            &lt;&lt; std::setw(6) &lt;&lt; (now() - start) / 1ms &lt;&lt; &quot;ms &quot; //
            &lt;&lt; id &lt;&lt; &quot; (&quot; &lt;&lt; ec.message() &lt;&lt; &quot;)&quot;              //
            &lt;&lt; std::endl;
    };
}

struct Test1 {
    Test1(asio::io_context&amp; io) : m_ex{make_strand(io)} {
        t1.async_wait(bind_executor(m_ex, handler(1)));
        t2.async_wait(bind_executor(m_ex, handler(2)));
    }

  private:
    asio::any_io_executor m_ex;
    asio::steady_timer t1{m_ex, 100ms}, t2{m_ex, 200ms};
};

struct Test2 {
    Test2(asio::io_context&amp; io) : m_ex{make_strand(io)} {
        t1.async_wait(handler(3));
        t2.async_wait(handler(4));
    }

  private:
    asio::any_io_executor m_ex;
    asio::steady_timer t1{m_ex, 300ms}, t2{m_ex, 400ms};
};

struct Test3 {
    template &lt;typename Ex&gt; Test3(Ex ex) : t1{ex, 500ms}, t2{ex, 600ms} {
        t1.async_wait(handler(5));
        t2.async_wait(handler(6));
    }

  private:
    asio::steady_timer t1, t2;
};

int main() {
    asio::io_context ioc;

    Test1 t1{ioc};
    Test2 t2{ioc};
    Test3 t3{make_strand(ioc)};

    ioc.run();
}

Prints

   100ms 1 (Success)
   200ms 2 (Success)
   300ms 3 (Success)
   400ms 4 (Success)
   500ms 5 (Success)
   600ms 6 (Success)

The third one has a distinct benefit of not hardcoding the executor type, which is beneficial in case you... use a different kind of context. Like, for example one that uses a thread pool.

To avoid templating the constructor, consider using the default type-erased executor type used by all IO objects:

Live On Coliru

#include &lt;boost/asio.hpp&gt;
#include &lt;iostream&gt;
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;

struct Ultimate {
    Ultimate(asio::any_io_executor ex) : t1{ex, 1s}, t2{ex, 2s} {
        std::cout &lt;&lt; std::unitbuf;
        t1.async_wait([](error_code) { std::cout &lt;&lt; &quot;1 &quot;; });
        t2.async_wait([](error_code) { std::cout &lt;&lt; &quot;2 &quot;; });
    }

  private:
    asio::steady_timer t1, t2;
};

int main() {
    asio::thread_pool ioc;

    Ultimate t3{make_strand(ioc)};

    ioc.join();
}

将对象的所有异步操作正确地放入一个`boost::asio::strand`中的方法。

huangapple
  • 本文由 发表于 2023年3月4日 01:32:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75630192.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定