英文:
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 <boost/asio.hpp>
#include <iostream>
#include <thread>
class Printer1
{
public:
Printer1(boost::asio::io_context& 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& error){std::cout << "1 ";}));
m_timer2.async_wait(boost::asio::bind_executor(m_strand, [this](const boost::system::error_code& error){std::cout << "2 ";}));
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> 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& 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& error){std::cout << "1 ";});
m_timer2.async_wait([this](const boost::system::error_code& error){std::cout << "2 ";});
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
boost::asio::steady_timer m_timer1;
boost::asio::steady_timer m_timer2;
};
or
class Printer3
{
public:
template <typename Executor>
Printer3(Executor executor)
: m_timer1{executor}
, m_timer2{executor}
{
m_timer1.async_wait([this](const boost::system::error_code& error){std::cout << "1 ";});
m_timer2.async_wait([this](const boost::system::error_code& error){std::cout << "2 ";});
}
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 <boost/asio.hpp>
#include <iostream>
#include <thread>
class Printer1
{
public:
Printer1(boost::asio::io_context& 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& error){std::cout << "1 ";}));
m_timer2.async_wait(boost::asio::bind_executor(m_strand, [this](const boost::system::error_code& error){std::cout << "2 ";}));
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> 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& 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& error){std::cout << "1 ";});
m_timer1.async_wait([this](const boost::system::error_code& error){std::cout << "2 ";});
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
boost::asio::steady_timer m_timer1;
boost::asio::steady_timer m_timer2;
};
or
class Printer3
{
public:
template <typename Executor>
Printer3(Executor executor)
: m_timer1{executor}
, m_timer2{executor}
{
m_timer1.async_wait([this](const boost::system::error_code& error){std::cout << "1 ";});
m_timer1.async_wait([this](const boost::system::error_code& error){std::cout << "2 ";});
}
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:
所有示例都被简化了:
#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对象使用的默认类型擦除执行器类型:
#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();
}
英文:
> 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:
#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:
#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([](error_code) { std::cout << "1 "; });
t2.async_wait([](error_code) { std::cout << "2 "; });
}
private:
asio::steady_timer t1, t2;
};
int main() {
asio::thread_pool ioc;
Ultimate t3{make_strand(ioc)};
ioc.join();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论