The following example completes with no assertions:
#include <cassert>
#include <functional>
#include <future>
#include <thread>
#include <boost/asio.hpp>
class example1
{
public:
typedef boost::asio::io_context io_context;
typedef boost::asio::io_context::executor_type executor_type;
typedef boost::asio::strand<executor_type> strand;
typedef boost::asio::executor_work_guard<executor_type> work_guard;
typedef std::function<void()> handler;
example1()
: work_(boost::asio::make_work_guard(context_)),
thread_([this]() { context_.run(); }),
strand1_(context_.get_executor()),
strand2_(context_.get_executor())
{
}
~example1()
{
assert(result_.get_future().get());
work_.reset();
thread_.join();
}
void invoke()
{
handler handle = boost::asio::bind_executor(strand2_,
std::bind(&example1::strand2_handler, this));
boost::asio::post(strand1_,
std::bind(&example1::strand1_handler, this, handle));
}
void strand1_handler(handler handle)
{
assert(strand1_.running_in_this_thread());
handle();
}
void strand2_handler()
{
assert(strand1_.running_in_this_thread());
////assert(strand2_.running_in_this_thread());
result_.set_value(true);
}
private:
io_context context_;
work_guard work_;
std::thread thread_;
strand strand1_;
strand strand2_;
std::promise<bool> result_;
};
int main()
{
example1 test{};
test.invoke();
}
However my expectation is that the commented-out assertion should succeed, as opposed to the one directly above it. According to strand::running_in_this_thread()
the handler handle
has been invoked in the caller's strand, not that provided to bind_executor
.
I can work around this using intermediate methods, as follows.
class example2
{
public:
typedef boost::asio::io_context io_context;
typedef boost::asio::io_context::executor_type executor_type;
typedef boost::asio::strand<executor_type> strand;
typedef boost::asio::executor_work_guard<executor_type> work_guard;
typedef std::function<void()> handler;
example2()
: work_(boost::asio::make_work_guard(context_)),
thread_([this]() { context_.run(); }),
strand1_(context_.get_executor()),
strand2_(context_.get_executor())
{
}
~example2()
{
assert(result_.get_future().get());
work_.reset();
thread_.join();
}
void invoke()
{
handler handle =
std::bind(&example2::do_strand2_handler, this);
boost::asio::post(strand1_,
std::bind(&example2::strand1_handler, this, handle));
}
void strand1_handler(handler handle)
{
assert(strand1_.running_in_this_thread());
handle();
}
// Do the job of bind_executor.
void do_strand2_handler()
{
boost::asio::post(strand2_,
std::bind(&example2::strand2_handler, this));
}
void strand2_handler()
{
////assert(strand1_.running_in_this_thread());
assert(strand2_.running_in_this_thread());
result_.set_value(true);
}
private:
io_context context_;
work_guard work_;
std::thread thread_;
strand strand1_;
strand strand2_;
std::promise<bool> result_;
};
int main()
{
example2 test2{};
test2.invoke();
}
But avoiding that is presumably the purpose of
bind_executor
. Is this a boost bug or am I missing something? I've tried following this through the boost::asio sources but to no avail.
Update
Thanks to @sehe for a lot of help. The above problem can be resolved in a number of ways, for example:
class example3
{
public:
typedef boost::asio::io_context io_context;
typedef boost::asio::io_context::executor_type executor_type;
typedef boost::asio::strand<executor_type> strand;
typedef boost::asio::executor_work_guard<executor_type> work_guard;
typedef boost::asio::executor_binder<std::function<void()>,
boost::asio::any_io_executor> handler;
example3()
: work_(boost::asio::make_work_guard(context_)),
thread_([this]() { context_.run(); }),
strand1_(context_.get_executor()),
strand2_(context_.get_executor())
{
}
~example3()
{
assert(result_.get_future().get());
work_.reset();
thread_.join();
}
void invoke()
{
auto handle = boost::asio::bind_executor(strand2_,
std::bind(&example3::strand2_handler, this));
boost::asio::post(strand1_,
std::bind(&example3::strand1_handler, this, handle));
}
void strand1_handler(handler handle)
{
assert(strand1_.running_in_this_thread());
boost::asio::dispatch(handle);
}
void strand2_handler()
{
assert(strand2_.running_in_this_thread());
result_.set_value(true);
}
private:
io_context context_;
work_guard work_;
std::thread thread_;
strand strand1_;
strand strand2_;
std::promise<bool> result_;
};
int main
{
example3 test3{};
test3.invoke();
}