4

i want to make container of futures ,each future is void result of a task so that i could use wait_for_any on the container ,each task is coroutine which i currently implement using yield_context,and inside this coroutine there initiating function which returns ec and result where i use ec to analyze result.and then another coroutine is called passes same yield_context .
i want to know how to make this design.
and if i ll use use_future ,how can i pass error code to ec not throwing it unless there is no way except throwing it ,in this case i ll put try and catch around async initiating functions.
all these tasks will be posted ,spawned ... on asio io_service .
this is my main parts of code:
this is the spawn of task

boost::asio::spawn(GetServiceReference(), boost::bind(&HTTPRequest::Execute, boost::placeholders::_1, m_HttpClient_request_name, Get_mHTTPClient_Responses_Map()));

and this is the coroutine using yield_context

void HTTPRequest::Execute(boost::asio::yield_context yield_r, std::string request_name, std::map<std::string, boost::shared_ptr<HTTPResponse>>& mHTTPClient_Responses_Map)
{
    resolver_iterator iterator_connect = boost::asio::async_connect(mSock, iterator_resolve, yield_r[ec]);
}

and inside Execute we use ec to analyze

if (ec == boost::system::errc::errc_t::success){}

and here we start another coroutine passing same yield_context

SendRequest(yield_r);
}

i want to change this so i have container of futures for all spawned Execute,i do not care about results of Execute because i put them to member class Response.
But i need result in future so that i can use wait_any on the container .

ahmed allam
  • 377
  • 2
  • 15

1 Answers1

5

If you can change your implementation, use the async_result pattern.

This makes it so you can use your method with any of the approaches (completion handler, yield context or use_future).

I reproduce the self-contained example from here for inspiration:

Comprehensive Demo

Showing how to use it with with

  • coro's and yield[ec]
  • coro's and yield + exceptions
  • std::future
  • completion handlers

Live On Coliru

#define BOOST_COROUTINES_NO_DEPRECATION_WARNING 
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/use_future.hpp>

using boost::system::error_code;
namespace asio = boost::asio;

template <typename Token>
auto async_meaning_of_life(bool success, Token&& token)
{
#if BOOST_VERSION >= 106600
    using result_type = typename asio::async_result<std::decay_t<Token>, void(error_code, int)>;
    typename result_type::completion_handler_type handler(std::forward<Token>(token));

    result_type result(handler);
#else
    typename asio::handler_type<Token, void(error_code, int)>::type
                 handler(std::forward<Token>(token));

    asio::async_result<decltype (handler)> result (handler);
#endif

    if (success)
        handler(error_code{}, 42);
    else
        handler(asio::error::operation_aborted, 0);

    return result.get ();
}

void using_yield_ec(asio::yield_context yield) {
    for (bool success : { true, false }) {
        boost::system::error_code ec;
        auto answer = async_meaning_of_life(success, yield[ec]);
        std::cout << __FUNCTION__ << ": Result: " << ec.message() << "\n";
        std::cout << __FUNCTION__ << ": Answer: " << answer << "\n";
    }
}

void using_yield_catch(asio::yield_context yield) {
    for (bool success : { true, false }) 
    try {
        auto answer = async_meaning_of_life(success, yield);
        std::cout << __FUNCTION__ << ": Answer: " << answer << "\n";
    } catch(boost::system::system_error const& e) {
        std::cout << __FUNCTION__ << ": Caught: " << e.code().message() << "\n";
    }
}

void using_future() {
    for (bool success : { true, false }) 
    try {
        auto answer = async_meaning_of_life(success, asio::use_future);
        std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";
    } catch(boost::system::system_error const& e) {
        std::cout << __FUNCTION__ << ": Caught: " << e.code().message() << "\n";
    }
}

void using_handler() {
    for (bool success : { true, false })
        async_meaning_of_life(success, [](error_code ec, int answer) {
            std::cout << "using_handler: Result: " << ec.message() << "\n";
            std::cout << "using_handler: Answer: " << answer << "\n";
        });
}

int main() {
    asio::io_service svc;

    spawn(svc, using_yield_ec);
    spawn(svc, using_yield_catch);
    std::thread work([] {
            using_future();
            using_handler();
        });

    svc.run();
    work.join();
}

Prints:

using_yield_ec: Result: Success
using_yield_ec: Answer: 42
using_yield_ec: Result: Operation canceled
using_yield_ec: Answer: 0
using_future: Answer: 42
using_yield_catch: Answer: 42
using_yield_catch: Caught: Operation canceled
using_future: Caught: Operation canceled
using_handler: Result: Success
using_handler: Answer: 42
using_handler: Result: Operation canceled
using_handler: Answer: 0

Note: for simplicity I have not added output synchronization, so the output can become intermingled depending on runtime execution order

sehe
  • 374,641
  • 47
  • 450
  • 633
  • i studied your answer during my search and it is already bookmarked ,but i find difficulty understanding what is really happening when completion token is converted to handler .all examples talks about using signature to form the handler type .but the formed handler is just prototype. there is no body for the handler.so what will be called when async operation finishes????.regarding my current question i am still confused about the order of operations.for example:i will make packaged tasks then add them to container...BUT do i launch ios.run from same thread or different one? – ahmed allam Apr 05 '20 at 04:12
  • completing comment:and where should i call:wait_for_any???in same thread where i made tasks OR after ios.run "either in same or different thread"??? – ahmed allam Apr 05 '20 at 04:14
  • 1
    now i think i got what handler means...it is just type determination.and there are like several cases "i think these are called type traits" which determine at compile time what is the type of handler passed and according to this they pass arguments of signature to the determined type" or part of arguments like in case of future"...So what will be called is the argument passed from user if it is function object"lambda,bind,callback,function pointer" or it is future making mechanism in case of use_future, or it is just switching contexts in case of stakeful coroutines or it is stakeless >>> – ahmed allam Apr 17 '20 at 17:43
  • coroutine in case move(coro) is passed.am i right????now how can we use these different completion tokens with spawn ,post???can we evade building the async_function and use packaged_task to convert function to task returning future ???is the resulting task same to async_function made??will we use async_function with packaged task OR using_future?? – ahmed allam Apr 17 '20 at 17:47
  • I upvoted the comment that I think I understood and makes a lot of sense. I don't know what you mean with all the questions (???) – sehe Apr 17 '20 at 22:22
  • when i try to use the functions in my code ,i have 2 options as far as i know for now,first is to avoid making composed function directly and try to use packaged_task to make task out of my function and then post this task>>>this leads to some errors which stopped my progress and i made https://stackoverflow.com/questions/61181774/can-we-make-composed-async-function-using-packaged-task to seek help about it.OR second option is to read about composed function and universal model"which is very interesting" but more complicated and it had several "need to know" subjects like type traits and – ahmed allam Apr 18 '20 at 08:10
  • perfect forwarding which lead to universal references and SFINAE and template aliases and macros which are so much interesting and i am glad to have read them"i always tried to avoid these difficult subjects" .So now i am trying to make composed function but wonder why i had error with my first approach using packaged_task with post.So i guessed that spawn is just used with composed operation using yield as completion token?is this right?...and packaged_task is just used with threads and can not be used with post"which requires copying of task and task is move_only,??is this right?? – ahmed allam Apr 18 '20 at 08:15
  • and during this i faced question: is function converted to task using packaged task IS EQUIVALENT to async composed function using use_future??thanks for your reply – ahmed allam Apr 18 '20 at 08:17
  • i made some additions to your sample code to try to understand async composed function.i added:boost::asio::steady_timer t(svc, boost::asio::chrono::seconds(45)); t.async_wait(&print); auto answer = async_meaning_of_life(true, asio::use_future); std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n"; – ahmed allam Apr 18 '20 at 14:56
  • 1
    Can you post a new question? In general, packaged_task is not equivalent, mainly because it erases the actual handler type. Handler types include information such as when a strand is used. By erasing the type with packaged_task, ASIO loses the actual handler type and cannot maintain the semantics. – sehe Apr 18 '20 at 15:25
  • i made this question 5 days ago. https://stackoverflow.com/questions/61181774/can-we-make-composed-async-function-using-packaged-task. this is mainly about my initial problem .but i discovered that i have defecient understanding of several major things i am trying to get .i am trying some different cases with your code.i will post them once finished.i will accept your answer now and continue with modifications of it in next question – ahmed allam Apr 18 '20 at 18:58
  • and this is another question with modifications on your code to show my obscure points and my questions https://stackoverflow.com/questions/61329157/how-spawn-and-post-works-with-asio – ahmed allam Apr 20 '20 at 18:11