как spawn и post работают с asio? - PullRequest
2 голосов
/ 20 апреля 2020
// I asked this question
// https://stackoverflow.com/questions/61026135/asio-use-future-instead-of-yieldec
// and comments lead to need to modify code of answer and put in in this new
// question.
// I tried to ask questions in form  of code trials and causes of writing them
// or how i under stand them

// asio_packaged_task.cpp : Defines the entry point for the console application.

//#include "stdafx.h"
#define BOOST_COROUTINES_NO_DEPRECATION_WARNING
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/bind.hpp>
#include <iostream>

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); // 4-18-2020 this line happens when
                                   // async_meaning_of_life work is done,this
                                   // line is calling the handler and passing it
                                   // the result of  async_meaning_of_life
                                   // function which here for simplicity are
                                   // supplied as error_code{} and 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";
        }
}
// 4-18-2020 something interesting happens here,when we call the function
// using_future or using_handler in the same thread we get into these two
// functions then inside them we call async_meaning_of_life which is an
// initiating function ,the async_meaning_of_life has two parts: its code which
// ends before if(success) then it calls the completion token passed to it which
// is promise OR lambda "it might be fuction object ,functor,function pointer, "
// using handler(error,42) where handler represents the true handler type
// according to the token passed to function. then it returns the result by
// result.get to using_future or using_handler. inside using handler we notice
// that code returns back to lambda after handler(error,42) .if completion token
// were bind or function object,we would have seen code jumping to bound
// function or function object

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";
        });
}

void print(const boost::system::error_code& /*e*/) {
    std::cout << "Hello, world!" << std::endl;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Token>
auto async_meaning_of_life_composed(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

    // here i will add intermediate initiating functions

    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";
    });

    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";
    }

    // using_yield_ec(asio::yield_context yield);
    // spawn(svc, using_yield_ec);
    //////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////
    if (success)
        handler(error_code{}, 42); // 4-18-2020 this line happens when
                                   // async_meaning_of_life work is done,this
                                   // line is calling the handler and passing it
                                   // the result of  async_meaning_of_life
                                   // function which here for simplicity are
                                   // supplied as error_code{} and 42
    else
        handler(asio::error::operation_aborted, 0);

    return result.get();
}

void using_future_composed() {
    for (bool success : { true, false })
        try {
            auto answer =
                async_meaning_of_life_composed(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";
        }
}

int main() {
    asio::io_service svc;

    boost::asio::steady_timer t(svc, boost::asio::chrono::seconds(45));
    // this function returns immediately and make new thread

    t.async_wait(&print);
    // this function returns immediately>>>>also it adds 1 out standing work to
    // svc.is async_wait body runned in main threaed OR in another thread????if
    // it is ran in another thread,how immediate return happens"not
    // blocking"??why async_meaning is not returning immediately like
    // async_wait?

    auto answer = async_meaning_of_life(true, asio::use_future);
    // this function does not return immediately and is executing in main thread
    // >>>>>how can we make it behave like async_wait???? first

    std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";
    svc.post([]() { // this adds 1 outstanding work to svc and does not start
        auto answer = async_meaning_of_life(true, asio::use_future);
        std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";
    });
    svc.post(using_future);

    // this increase outstanding work by 1

    // boost::asio::yield_context yield;
    // 4-18-2020 this is only used with spawn ,if you want to use stakeful
    // coroutines,use push and pull types of coroutine "i wonder how to do
    // this???"

    // using_yield_ec( yield);this is wrong usage
    // using_yield_catch( yield);this is wrong usage

    // using_future();this is normal usage but it does not return immediately
    // and it executes in main thread.
    // using_handler();
    svc.post(using_future_composed);
    spawn(svc, using_yield_ec);
    // this adds 2 outstanding work to svc why 2 works are made while we are
    // launching one function????

    spawn(svc, using_yield_catch);
    // what i think i understand about mechanism of work of spawn:spawn is
    // called from main thread>>>>it is just used with coroutines taking
    // yield_context as argument,spawn post function to service,spawn makes link
    // between the context in which service will be ran"may be main thread or
    // new thread AND the context of coroutine function ran in same thread as
    // service" or may be the coroutine makes new thread in which it is
    // running???".Then when svc.run is called,svc calls task"here svc is caller
    // and coroutine is callee",task is executing,yield is called as completion
    // token"can we call yield outside initiating function to switch to caller
    // "here caller is svc"????. then we are now in svc context which calls
    // another task .....

    // t.async_wait(&using_future);wrong usage leading to error?why can not in
    // use using_future function as completion callback with async_wait???

    // spawn(svc, using_future);wrong usage as using_future is not coroutine?

    std::thread work([] {
        using_future();
        using_handler();
        auto answer = async_meaning_of_life(true, asio::use_future);
        // this function does not return immediately and is executing in main
        // thread >>>>>how can we make it behave like async_wait???? first

        std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";

    });
    std::thread work_io([&] { // this starts new thread in which svc is run
        svc.run();
    });

    svc.run(); // this run svc in main thread

    // general question:
    /*
    using_* is considered normal function or coroutine OR composed operation??
    async_meaning is considered initiating function?

    why does not it return immediately when ran in main thread?how can we make
    it return immediately and then when certain result is present ,it calls its
    callback??

    async_wait is considered initiating function? why does it return
    immediately then when timer expires ,it calls back its completion token??

    can i make the following composed operation:

    i will make composed operation which returns future to caller thread,

    and inside it i shall call another composed operation with coroutine,
    */

    work.join();
    work_io.join();
}

1 Ответ

2 голосов
/ 21 апреля 2020

boost::asio::steady_timer t(svc, boost::asio::chrono::seconds(45));
// this function returns immediately and make new thread

Нет, это не создает новую тему. Он просто создает сервисный объект (таймер) и возвращает. очевидно сразу, как std::string s("hello"); возвращается при построении строки.

t.async_wait(&print);
// this function returns immediately>>>>also it adds 1 out standing work to
// svc. is async_wait body runned in main threaed OR in another thread????if
// it is ran in another thread,how immediate return happens"not
// blocking"??why async_meaning is not returning immediately like
// async_wait?

Замедление.

это async_wait тело, запущенное в основном потоке ИЛИ в другом потоке?

Это просто функция. Он работает в текущем потоке. Например, когда вы вызывали printf.

, если он запускается в другом потоке, как немедленный возврат происходит "не блокируя"?

Ну, это не в другом потоке , Но если бы это было так, то было бы очевидно, как он вернул бы «не блокирование»: потому что работа не выполняется в текущем потоке.

Почему async_meaning_of_life не возвращается сразу, как async_wait?

Возвращается немедленно.

Теперь немного хитрее: даже если вы используете его с yield_context (внутри сопрограммы). Он немедленно вернется и вызовет сопрограмму. Это означает, что другие задачи могут запускаться в служебном потоке (ах), и только после завершения операции asyn c сопрограмма будет возобновлена. С точки зрения сопрограммы, это будет выглядеть так, как если бы звонок блокировался. В этом весь смысл (стопки) сопрограмм. Он «абстрагирует» асинхронность.

Итак, да, async_meaning_of_life всегда (всегда) возвращает (почти) немедленно.


svc.post([]() { // this adds 1 outstanding work to svc and does not start

Правильно. Используйте функцию {poll|run}[_one,_for,_until] для запуска задач².


    auto answer = async_meaning_of_life(true, asio::use_future);
    std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";

Здесь вы ничего не спрашиваете, но использование будущего, просто чтобы дождаться его, является анти-паттерном ». Это абсолютно бесполезно, поскольку оно всегда будет генерировать блокирующее поведение.

Вы должны где-то хранить будущее, выполнять другую работу, а затем, когда вам понадобится результат будущего (а он может или не может быть уже завершен), вы ожидаете его (.get() Вы должны хранить будущее где-нибудь, выполняйте другую работу, а затем, когда вам нужен результат будущего (а он может или не может быть уже завершен), вы ожидаете его (например, вызывая .get()).


// using_yield_ec( yield);this is wrong usage
// using_yield_catch( yield);this is wrong usage

Правильно. При правильном использовании сервис Asio предоставит вам контекст урожайности.

// boost::asio::yield_context yield;
// 4-18-2020 this is only used with spawn ,if you want to use stakeful
// coroutines,use push and pull types of coroutine "i wonder how to do
// this???"

Не знаю. Просто обратитесь к документации Boost Coroutine (я предлагаю Boost Coroutine2). Это вне топи c для Asio asyn c операций.


// using_future();this is normal usage but it does not return immediately
// and it executes in main thread.

Ну, да. Вы взяли это из минимального примера, который показывает ТОЛЬКО механику различных токенов async_result.

Просто обратитесь к нескольким строчкам выше:

Вы должны хранить будущее где-нибудь, выполнять другую работу, а затем, когда вам нужен результат будущего (а он может или не может быть уже завершено) вы ждете его (.get() Вы должны где-то хранить будущее, выполнять другую работу, а затем, когда вам нужен результат будущего (а он может или не может быть уже завершен), вы ожидаете его (например, вызывая .get()).


svc.post(using_future_composed);

Опять же, я не вижу вопросов, но я не думаю, что это означает, что вы понимаете это. Я шагаю.

Я вижу, using_future_composed в основном using_future, но вместо этого звонит async_meaning_of_life_composed.

Теперь, глядя на async_meaning_of_life_composed Я понятия не имею, что он должен делать. Это похоже на async_meaning_of_life с добавлением случайных строк кода, выполняющих все виды операций, включая операции блокировки (см. Anti-pattern¹) в функции, которая должна планировать только асинхронную операцию c.

Это просто не то, что вы хотите сделать. Когда-либо.


spawn(svc, using_yield_ec);
// this adds 2 outstanding work to svc why 2 works are made while we are
// launching one function????

Честно говоря, я не знаю. Я предполагаю, что это потому, что запуск самого coro размещен в рабочей очереди, поэтому он запускается исключительно в исключительных ситуациях из одного из рабочих потоков.

Здесь важнее то, что вы еще не запустили io-работники, см. [²] выше.


spawn(svc, using_yield_catch);
// what i think i understand about mechanism of work of spawn:spawn is
// called from main thread>>>>it is just used with coroutines taking
// yield_context as argument,spawn post function to service,spawn makes link
// between the context in which service will be ran"may be main thread or
// new thread AND the context of coroutine function ran in same thread as
// service"...

Хм, в основном, да.

//          ... or may be the coroutine makes new thread in which it is
// running???" ...

Определенно нет. И Coroutines, и Asio являются устройством / средой для достижения параллелизма без необходимости многопоточности. Сопрограмма никогда не создаст тему. Asio обычно не создает какие-либо потоки (если только для реализации определенных видов сервисов на некоторых платформах не будет, но они будут деталями реализации, а ваши задачи / обработчики никогда не будут работать на таких скрытая тема).

//         ... .Then when svc.run is called,svc calls task"here svc is caller
// and coroutine is callee",task is executing,yield is called as completion
// token"can we call yield outside initiating function to switch to caller
// "here caller is svc"????. then we are now in svc context which calls
// another task .....

Да. Нет, yield_context не является порталом к ​​другому пространственно-временному континууму.

Я не совсем уверен, что вы имеете в виду под "'yield yield", поэтому, когда вы думаете о том, чтобы вызывать его извне инициирующего функция, я бы сказал: вероятно, не делайте этого.


// t.async_wait(&using_future);wrong usage leading to error?why can not in
// use using_future function as completion callback with async_wait???

Поскольку она не удовлетворяет требованиям обработчика для steady_time::async_wait (который должен занимать только boost::system::error_code. Вы, возможно, имеете в виду use_future (от Asio) вместо своего using_future?

auto ignored_future = t.async_wait(boost::asio::use_future);

Я допускаю, что имена несколько сбивают с толку. Если это поможет, переименуйте все функции using_XYZ в demonstration_using_XYZ .


// spawn(svc, using_future);wrong usage as using_future is not coroutine?

Вы правильно поняли.


std::thread work([] 
    using_future();
    using_handler();
    auto answer = async_meaning_of_life(true, asio::use_future);
    // this function does not return immediately and is executing in main
    // thread >>>>>how can we make it behave like async_wait???? first

    std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";

});

Полагаю, вы просто скопировали / вставили комментарий, но на случай, если вы действительно обеспокоены: нет, это не так не запускается в главном потоке. Он запускается в потоке work, и да, это потому, что вы блокируете в future::get(). См. выше¹.


std::thread work_io([&] { // this starts new thread in which svc is run
    svc.run();
});

Лучше поздно, чем никогда :)

svc.run(); // this run svc in main thread

Правильно, и больше бегать не повредит. Запуск службы в нескольких потоках может требует синхронизации обработчика: Зачем мне нужно прядь на соединение при использовании boost :: asio?


// general question:
/*
using_* is considered normal function or coroutine OR composed operation??

Нормальный функции (см. пояснение о переименовании в demonstration_using_XYZ выше)

async_meaning is considered initiating function?

Правильно.

why does not it return immediately when ran in main thread? 

Да. Смотри выше. Если вы имеете в виду, почему ваша собственная функция async_meaning_of_life_composed bblock? Это потому, что вы заставили его выполнять операции блокировки (см. Выше).

how can we make
it return immediately and then when certain result is present ,it calls its
callback??

Обычный способ сделать это - запустить другие операции asyn c. Скажем, например, вы ждете завершения сетевой операции (асинхронно, например, с помощью boost::asio::async_write), а когда она закончится, вы вызываете handler. Помощник async_result делает так, что вам не нужно знать фактический completion_handler_type, и он «волшебным образом» будет делать правильные вещи независимо от того, как была вызвана ваша инициирующая функция.

async_wait is considered initiating function? why does it return
immediately then when timer expires ,it calls back its completion token??

Потому что Вот как создаются операции asyn c. Они были разработаны таким образом, потому что это полезное поведение.

can i make the following composed operation:

i will make composed operation which returns future to caller thread,

and inside it i shall call another composed operation with coroutine,
*/

Вы можете запустить сопрограмму. Просто убедитесь, что вы передаете право собственности на результат async_result, чтобы можно было оттуда вызывать обработчик, чтобы сигнализировать о завершении вашей операции.

В случае фьючерсов обычный способ составления операций - это составление фьючерсов, например : https://www.boost.org/doc/libs/1_72_0/doc/html/thread/synchronization.html#thread .synchronization.futures.then

std::string someotheroperation(int);

future<int> fut1 = foo();
future<std::string> fut2 = foo().then(someotheroperation);

БОНУС

Основная часть документации по написанию составных операций с Asio (по иронии судьбы) эта страница в Зверь документация. Возможно, просмотр еще нескольких реальных примеров может дать вам больше идей.

Имейте в виду, Beast поставляется с несколькими средствами, которые делают обслуживание библиотеки для / them / немного проще, но вполне могут быть излишними для вашего собственного приложения. , Опять же, если вы ошибетесь на своем пути, вы не пропустите важные вещи, подобные тем, которые мы обсуждали здесь ранее:

enter image description here

...