Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::asio::spawn yield as callback

I'm trying to rewrite a project using boost::asio::spawn coroutines. Some parts of the project cannot be changed. For example, the storage protocol library is also written with boost::asio, but without coroutines.

The problem is how to convert yield_context into a normal callback (a boost::function object or a classical functor).

This is what we have in the storage library API:

void async_request_data(uint64_t item_id, boost::function< void(Request_result *) > callback);

As we know from examples, the asio yield context can be used like this:

    my_socket.async_read_some(boost::asio::buffer(data), yield);

In this case a boost::asio::yield_context object serves as a callback for async_read_some. I would like to pass a yield object as the second argument to async_request_data, so i can use it in a synchronous manner.

How can this be done? I think it may be possible via some proxy-object, possibly using an approach based on asio_handler_invoke. But I am having trouble seeing how to do this.

like image 785
PSIAlt Avatar asked Jun 30 '14 19:06

PSIAlt


4 Answers

Looks like the best documentation for this feature can be found in a C++ standard proposal written by the boost asio author:

N4045 – Library Foundations for Asynchronous Operations, Revision 2

See section 9.1 which says:

handler_type_t<CompletionToken, void(error_code, size_t)>   #3
  handler(std::forward<CompletionToken>(token));

3: The completion token is converted into a handler, i.e. a function object to be called when the asynchronous operation completes. The signature specifies the arguments that will be passed to the handler.

I guess in your case the CompletionToken template argument will actually be boost::asio::yield_context and handler_type converts it to a callback object.


Here is the code from section 9.1 updated to call your async_request_data function:

template <class CompletionToken>
auto async_foo(uint64_t item_id, CompletionToken&& token)
{
  handler_type_t<CompletionToken, void(Request_result *)>
    handler(std::forward<CompletionToken>(token));

  async_result<decltype(handler)> result(handler);  

  async_request_data(item_id, handler);

  return result.get();  
}
like image 114
free_coffee Avatar answered Nov 11 '22 17:11

free_coffee


Thanks to @PSIAlt and @free_coffee I know how to use callback functions in stackful coroutine.

Here is a simple example just for asio newbies(like me :D)

https://gist.github.com/chenfengyuan/4d764b0bca82a42c05a9

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/asio/spawn.hpp>
#include <memory>

void bar(boost::asio::io_service &io, std::function<void()> cb){
    auto ptr = std::make_shared<boost::asio::deadline_timer>(io, boost::posix_time::seconds(1));
    ptr->async_wait([ptr, cb](const boost::system::error_code&){cb();});
}

template<typename Handler>
void foo(boost::asio::io_service &io, Handler && handler){
    typename boost::asio::handler_type<Handler, void()>::type handler_(std::forward<Handler>(handler));
    boost::asio::async_result<decltype(handler_)> result(handler_);
    bar(io, handler_);
    result.get();
    return;
}

int main()
{
  boost::asio::io_service io;
  boost::asio::spawn(io, [&io](boost::asio::yield_context yield){
      foo(io, yield);
      std::cout << "hello, world!\n";
  });

  io.run();

  return 0;
}
like image 22
cfy Avatar answered Nov 11 '22 17:11

cfy



Great thanks to free_coffe i managed this to work. Posting solution for my case, possibly someone need it.

template <class CompletionToken>
RequestResult async_foo(Packet &pkt, CompletionToken&& token) {
   typename boost::asio::handler_type< CompletionToken, void(RequestResult) >::type handler( std::forward<CompletionToken>(token) );
  boost::asio::async_result<decltype(handler)> result(handler);
  storage_api->writePacket(pkt, handler);
  return result.get();
}

Later we can use this proxy:

RequestResult res = async_foo(pkt, std::forward<boost::asio::yield_context>(yield) );
like image 3
PSIAlt Avatar answered Nov 11 '22 17:11

PSIAlt


Nowadays, one can treat callback handlers and yield_context (as well as use_awaitable and use_future) uniformly as completions tokens that are accepted via a templatized argument. The token is then transformed into a handler function that is passed to an initiating function via boost::asio::async_initiate:

// This is the API function
template <typename CompletionToken>
auto async_request_db_data(uint64_t item_id, CompletionToken&& token)
{
    // If you can't use C++14 generic lambdas, then use a function
    // object instead with an operator() template member function.
    auto init = [](auto completion_handler, uint64_t item_id)
    {
        init_request_db_data(item_id, std::move(completion_handler));
    }

    // void(database_result) is the signature of the intermediary
    // handler function that is passed to the initiating function.
    return boost::asio::async_initiate<Token, void(database_result)>(
        init, std::forward<CompletionToken>(token));

}

// This is a private function that does the actual work.
template <typename CompletionHandler>
void init_request_db_data(uint64_t item_id, CompletionHandler handler)
{
    // Perform actual work and then post handler when complete,
    // passing the database_result.
    // Note the handler below is moved because some completion handlers
    // are move-only (particularly those originating from use_awaitable).

    auto exec = boost::asio::get_associated_executor(handler);
    boost::asio::post(exec, std::bind(std::move(handler), db_result);
}

int main()
{
    boost::asio::io_context ioctx;

    // API used with stackful coroutine yield_context
    boost::asio::spawn(
        ioctx,
        [](boost::asio_yield_context yield)
        {
            auto result = async_request_db_data(42, yield);
        });

    // API used with traditional callback handler
    async_request_db_data(
        42,
        [](database_result result)
        {
            // Do something with result
        }

    ioctx.run();
}

If you need to type-erase the handler passed to the initiating function (because you want the implementation to be compiled from a cpp file), then check out the experimental any_completion_handler that the author of Boost.Asio has recently written (see related feature request)

like image 1
Emile Cormier Avatar answered Nov 11 '22 18:11

Emile Cormier