在Boost.Asio栈式协程中的屈服(Yielding)

6
使用Boost.Asio的协程时,我该如何“手动”进行yield以便给另一个协程或异步操作运行的机会?例如,在从TCP套接字接收到命令后,我需要执行长时间计算才能发送响应:
asio::spawn(strand_, [this, self](asio::yield_context yield)
{
    char data[256];
    while (socket_.is_open())
    {
        size_t n = socket_.async_read_some(boost::asio::buffer(data),
                                           yield);

        if (startsWith(data, "computePi"))
        {
            while (!computationFinished)
            {
                computeSomeMore();
                yield; // WHAT SHOULD THIS LINE BE?
            }

            storeResultIn(data);
            boost::asio::async_write(socket_, boost::asio::buffer(data, n),
                                     yield);
        }
    }
});
3个回答

5

这比你想象的要简单:


涉及IT技术相关内容。
iosvc.post(yield);

会起到作用。

(iosvc 取自 @sehe 的示例代码)


这看起来确实更清晰。 - sehe
虽然之前很好用,但在Boost 1.80中已不再可行。请看我的回答。 - Emile Cormier

4
你可以在io_service对象上调用poll_one()
完整的工作示例:
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/thread.hpp>
#include <iostream>

namespace asio = boost::asio;
using boost::asio::ip::tcp;
using std::begin;
using std::end;

bool computationFinished = false;
void computeSomeMore() {
    static int count = 0;
    if (count++>10)
    {
        computationFinished = true;
        std::cout << "Calculation finished\n";
    } else
    {
        std::cout << "Calculating...\n";
        boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
    }
}

template <typename T> void storeResultIn(T& a) {
    std::fill(begin(a), end(a), '4');
}

int main()
{
    asio::io_service iosvc;
    tcp::socket s(iosvc);
    tcp::resolver r(iosvc);

    tcp::acceptor a(iosvc, tcp::endpoint(tcp::v4(), 6767));

    a.accept(s);
    {
        asio::spawn(iosvc, [&iosvc,&s](asio::yield_context yield)
        {
            char data[256];
            while (s.is_open())
            {
                size_t n = s.async_read_some(boost::asio::buffer(data), yield);

                if (boost::algorithm::starts_with(data, "computePi"))
                {
                    iosvc.post([]{std::cout << "I can still breath\n";}); // some demo work
                    iosvc.post([]{std::cout << "And be responsive\n";});

                    while (!computationFinished)
                    {
                        computeSomeMore();
                        iosvc.poll_one(); // this enables the demo work to be run
                    }

                    storeResultIn(data);
                    boost::asio::async_write(s, boost::asio::buffer(data, n), yield);
                } else
                {
                    std::cout << "Received unknown command '" << std::string(data, data+n) << "'\n";
                }
            }
        });
    }

    iosvc.run();
    std::cout << "Bye bye\n";
}

当发送“computePi”时,服务器会打印:

Calculating...
I can still breath
Calculating...
And be responsive
Calculating...
Calculating...
Calculating...
Calculating...
Calculating...
Calculating...
Calculating...
Calculating...
Calculating...
Calculation finished

3
这是必不可少的技能。我要求自己在可能时测试我的答案。这是很好的练习,也很有趣。 - sehe
使用 iosvc.poll() 代替 iosvc.poll_one() 在长时间计算进行时是否能使其余异步任务更加响应? - Emile Cormier
如果注意避免无限递归,那么在异步处理程序中调用iosvc.poll*是否安全,因为异步处理程序是在iosvc.poll或iosvc.run的上下文中调用的? - Emile Cormier
这是安全的。http://www.boost.org/doc/libs/1_56_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.thread_safety - sehe
1
我只是想强调一下,虽然在轮询io_service或将yield_context发布到io_service中都会导致协作式单线程多任务处理,但轮询io_service可以避免上下文切换开销,但是处理程序中未处理的异常将会解除并销毁协程。 - Tanner Sansbury
显示剩余4条评论

0

从Boost 1.80开始,Jamboree的答案不再适用。对我而言有效的是这个:

boost::asio::post(boost::asio::get_associated_executor(yield), yield);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接