Boost::Asio:Io_Service.Run() VS Poll() or How to Integrate Boost::Asio in Mainloop

Boost::Asio : io_service.run() vs poll() or how do I integrate boost::asio in mainloop

Using io_service::poll instead of io_service::run is perfectly acceptable. The difference is explained in the documentation

The poll() function may also be used
to dispatch ready handlers, but
without blocking.

Note that io_service::run will block if there's any work left in the queue

The work class is used to inform the
io_service when work starts and
finishes. This ensures that the
io_service object's run() function
will not exit while work is underway,
and that it does exit when there is no
unfinished work remaining.

whereas io_service::poll does not exhibit this behavior, it just invokes ready handlers. Also note that you will need to invoke io_service::reset on any subsequent invocation to io_service:run or io_service::poll.

boost asio io_service.run()

"until all work has finished and there are no more handlers to be dispatched, or until the io_service has been stopped"

Notice that you DO install a handler, named handle_accept, that reinstalls itself at each execution. Hence, the io_service.run will never return, at least until you quit it manually.

Basically, at the moment you run io_service.run in a thread, io_services proactor takes over program flow, using the handler's you installed. From that point on, you handle the program based on events (like the handle_accept) instead of normal procedural program flow. The loop you're mentioning is somewhere deep in the scary depths of the asio's proactor ;-).

Why do we need to use boost::asio::io_service::work?

When the io_service::run method is called without a work object, it will return right away. Typically, that is not the behavior most developers are looking for. There are of course some exceptions, but most developers are looking to specify a thread to handle all of the asynchronous processing and don't want that thread to exit until told to do so. That is what your code example does.

The io_service::run method is specified as a delegate or function pointer in the create_thread methods. So, when the thread is created from the create_thread method it will call the io_service::run method and it passes the io_service object as an argument. Typically one io_service object can be used with multiple socket objects.

The stop method is usually called when shutting down the application or when communication between all clients/servers is no longer required and it is not anticipated that any new connections will need to be initiated.

avoiding busy wait with boost::asio poll

I'd say, simply take advantage of the fact that boost::asio::io_service is fully threadsafe by default, and do

iosvc.run();

And signal service shutdown on "another thread in the service" like you would:

iosvc.stop();

Remember to iosvc.reset() before you call {run,poll}[_one] again, as per the documentation.

Of course you can also use other means to signal the actual logical workers to end, but then that's completely independent unrelated to Boost Asio

Integrate boost::asio into file descriptor based eventloops (select/poll)

Based on the example in this answer I came up with this solution that uses a generic handler, which writes into a wake-up pipe and then posts the handler call into another io_service. The read end of the pipe can be used in a file descriptor based event loop and the callback run_handler() is called from there, which clears the pipe and runs pending handlers in the main thread.

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

/// @brief Type used to emulate asynchronous host resolution with a
/// dedicated thread pool.
class resolver {
public:
resolver(const std::size_t pool_size)
: work_(boost::ref(resolver_service_)) {
// Create wake-up pipe
pipe(pipe_);
fcntl(pipe_[0], F_SETFL, O_NONBLOCK);
// Create pool.
for (std::size_t i = 0; i < pool_size; ++i)
threads_.create_thread(boost::bind(&boost::asio::io_service::run,
&resolver_service_));
}

~resolver() {
work_ = boost::none;
threads_.join_all();
}

template <typename QueryOrEndpoint, typename Handler>
void async_resolve(QueryOrEndpoint query, Handler handler) {
resolver_service_.post(boost::bind(
&resolver::do_async_resolve<QueryOrEndpoint, Handler>, this,
query, handler));
}

// callback for eventloop in main thread
void run_handler() {
char c;
// clear wake-up pipe
while (read(pipe_[0], &c, 1) > 0);
// run handler posted from resolver threads
handler_service_.poll();
handler_service_.reset();
}

// get read end of wake up pipe for polling in eventloop
int fd() {
return pipe_[0];
}

private:
/// @brief Resolve address and invoke continuation handler.
template <typename QueryOrEndpoint, typename Handler>
void do_async_resolve(const QueryOrEndpoint& query, Handler handler) {
typedef typename QueryOrEndpoint::protocol_type protocol_type;
typedef typename protocol_type::resolver resolver_type;

// Resolve synchronously, as synchronous resolution will perform work
// in the calling thread. Thus, it will not use Boost.Asio's internal
// thread that is used for asynchronous resolution.
boost::system::error_code error;
resolver_type resolver(resolver_service_);
typename resolver_type::iterator result = resolver.resolve(query, error);

// post handler callback to service running in main thread
handler_service_.post(boost::bind(handler, error, result));
// wake up eventloop in main thread
write(pipe_[1], "*", 1);
}

private:
boost::asio::io_service resolver_service_;
boost::asio::io_service handler_service_;
boost::optional<boost::asio::io_service::work> work_;
boost::thread_group threads_;
int pipe_[2];
};

template <typename ProtocolType>
void handle_resolve(
const boost::system::error_code& error,
typename ProtocolType::resolver::iterator iterator) {
std::stringstream stream;
stream << "handle_resolve:\n"
" " << error.message() << "\n";
if (!error)
stream << " " << iterator->endpoint() << "\n";

std::cout << stream.str();
std::cout.flush();
}

int main() {
// Resolver will emulate asynchronous host resolution with a pool of 5
// threads.
resolver resolver(5);

namespace ip = boost::asio::ip;
resolver.async_resolve(
ip::udp::resolver::query("localhost", "12345"),
&handle_resolve<ip::udp>);
resolver.async_resolve(
ip::tcp::resolver::query("www.google.com", "80"),
&handle_resolve<ip::tcp>);
resolver.async_resolve(
ip::udp::resolver::query("www.stackoverflow.com", "80"),
&handle_resolve<ip::udp>);
resolver.async_resolve(
ip::icmp::resolver::query("some.other.address", "54321"),
&handle_resolve<ip::icmp>);

pollfd fds;
fds.fd = resolver.fd();
fds.events = POLLIN;

// simple eventloop
while (true) {
if (poll(&fds, 1, 2000)) // waiting for wakeup call
resolver.run_handler(); // call resolve handler
else
break;
}
}

What is the definition of a boost::asio::io_service ready handler?

int main()
{
boost::asio::io_service io_service;
boost::asio::deadline_timer timer(io_service);

timer.expires_from_now(boost::posix_time::seconds(5));
timer.async_wait([](const boost::system::error_code& err)
{ std::cout << (err ? "error" : "okay")
;});

//io_service.poll_one();
io_service.run_one();
}

If you use io_service.poll_one(); you will most likely not see any output because the timer has not elapsed yet. ready handler simply means a handle that is ready to run (such as when a timer elapses or an operation finishes, etc.). However, if you use io_service.run_one(); this call will block until the timer finishes and execute the handler.



Related Topics



Leave a reply



Submit