首页 > 解决方案 > spawn 和 post 如何与 asio 一起工作?

问题描述

// 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();
}

标签: boost-asio

解决方案



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 一起使用(在协程内)。它将立即返回并导致协程屈服。这意味着其他任务有机会在服务线程上运行,并且只有当异步操作完成时,协程才会恢复。从协程的角度来看,它看起来好像调用被阻塞了。这是(堆栈式)协程的重点。它“抽象”了异步性。

所以,是的,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)。这与 Asio 异步操作无关。


// 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添加了随机的代码行,在一个应该只安排异步操作的函数中做各种事情,包括阻塞操作(参见反模式¹)。

那不是你想做的。曾经。


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-workers,请参见上面的 [²]。


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???" ...

当然不。协程和 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它不是通往不同时空连续体的门户。

我不太清楚你所说的''call 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_composedbblock?那是因为你让它做阻塞操作(见上文)。

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

通常的做法是启动其他异步操作。比如说,您等待网络操作完成(异步,例如使用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??

因为这就是异步操作的设计方式。它们是这样设计的,因为这是有用的行为。

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 。然后

std::string someotheroperation(int);

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

奖金

关于使用 Asio 编写组合操作的最终文档(具有讽刺意味的是)是 Beast 文档中的此页面。也许看到更多现实生活中的例子可能会给你更多的想法。

请记住,Beast 附带了一些工具,可以使 /them/ 的库维护变得更容易一些,但对于您自己的应用程序来说,这很可能是矫枉过正。再说一次,如果您在自己的道路上犯了错误,您将不会忽略我们之前在这里讨论过的重要事情:

在此处输入图像描述


推荐阅读