asio如何更改可等待对象中的执行器?



我已经阅读了这个问题,并试图用以下代码复制答案:

#include <iostream>
#include <syncstream>
#include <thread>
#include <coroutine>
#include <boost/asio.hpp>
#include <boost/asio/experimental/as_single.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread/thread.hpp>
inline std::osyncstream tout() {
auto hash = std::hash<std::thread::id>{}(std::this_thread::get_id());
return std::osyncstream(std::cout) << "T" << hash << " ";
}
namespace asio = boost::asio;
asio::awaitable<void> mainCo(asio::io_context &appIO, asio::io_context &prodIO) {
// the thread should also change when using the IO contexts directly.
auto astrand = asio::io_context::strand{appIO};
auto pstrand = asio::io_context::strand{prodIO};
tout() << "MC on APPIO" << std::endl;
co_await asio::post(pstrand, asio::use_awaitable);
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(astrand, asio::use_awaitable);
tout() << "MC on APPIO" << std::endl;
co_await asio::post(pstrand, asio::use_awaitable);
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(pstrand, asio::use_awaitable); // nop - no operation because we are already on the correct execution_context
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(astrand, asio::use_awaitable);
tout() << "MC on APPIO" << std::endl;
}
int main() {
asio::io_context prodIO;
boost::thread prodThread;
{
// ensure the producer io context doesn't exit
auto prodWork = asio::make_work_guard(prodIO);
prodThread = boost::thread{[&prodIO] {
tout() << "ProdThread run start" << std::endl;
prodIO.run(); // if this call is removed the mainCo is stuck as expected
tout() << "ProdThread run done" << std::endl;
}};
asio::io_context appIO;
asio::co_spawn(appIO, mainCo(appIO, prodIO), asio::detached);
tout() << "MainThread run start" << std::endl;
appIO.run();
tout() << "MainThread run done" << std::endl;
}
prodThread.join();
return 42;
}

当前输出:

/tmp/tmp.wz38MWkttM/cmake-build-debug-remote-host/CoroContextSwitching
T14386720116392206644 MainThread run start
T8726023523478668610 ProdThread run start
T14386720116392206644 MC on APPIO
T14386720116392206644 MC on PRODIO
T14386720116392206644 MC on APPIO
T14386720116392206644 MC on PRODIO
T14386720116392206644 MC on PRODIO
T14386720116392206644 MC on APPIO
T14386720116392206644 MainThread run done
T8726023523478668610 ProdThread run done
Process finished with exit code 42

预期输出:

/tmp/tmp.wz38MWkttM/cmake-build-debug-remote-host/CoroContextSwitching
T14386720116392206644 MainThread run start
T8726023523478668610 ProdThread run start
T14386720116392206644 MC on APPIO
T8726023523478668610 MC on PRODIO
T14386720116392206644 MC on APPIO
T8726023523478668610 MC on PRODIO
T8726023523478668610 MC on PRODIO
T14386720116392206644 MC on APPIO
T14386720116392206644 MainThread run done
T8726023523478668610 ProdThread run done
Process finished with exit code 42

我希望线程id根据cout语句改变。然而,所有的cout语句都在主线程上执行。

我怎样才能得到想要的行为?

编辑2:

原来的问题仍然存在,这只是给问题增加了更多的信息。

似乎asio::use_awaitable从某个地方绑定了一个默认执行器。是否有记录的默认值?

使用以下编辑函数,我可以实现我想要的:

asio::awaitable<void> mainCo(asio::io_context &appIO, asio::io_context &prodIO) {
// the thread should also change when using the IO contexts directly.
auto astrand = asio::io_context::strand{appIO};
auto pstrand = asio::io_context::strand{prodIO};
tout() << "MC on APPIO" << std::endl;
co_await asio::post(pstrand, asio::bind_executor(pstrand, asio::use_awaitable)); // so use_awaitable is binding a default executor from somewhere.
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(astrand, asio::use_awaitable);
tout() << "MC on APPIO" << std::endl;
co_await asio::post(pstrand, asio::bind_executor(pstrand, asio::use_awaitable));
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(pstrand, asio::bind_executor(pstrand, asio::use_awaitable)); // nop - no operation because we are already on the correct execution_context
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(astrand, asio::use_awaitable);
tout() << "MC on APPIO" << std::endl;
co_await asio::post(astrand, /* the first parameter in post doesn't even matter (can be astrand, pstrand, appIO, prodIO) same result */
asio::bind_executor(pstrand, asio::use_awaitable));
tout() << "MC on PRODIO" << std::endl;
co_await asio::post(astrand, asio::use_awaitable);
tout() << "MC on APPIO" << std::endl;
}

然而,这似乎不是切换执行器的正确方法,因为asio::post的第一个参数无关紧要。那么正确的做法是什么呢?

编辑:这个问题是封闭的,并指出我这是否boost::asio co_spawn创建一个实际的线程?我知道co_spawn不会生成一个新线程。这就是为什么我自己生成一个名为prodThread的新线程。我希望execution_context在等待post语句后切换。链接的问题没有回答我的问题。

您希望执行器与完成令牌关联,然后让post/dispatch/defer从那里计算出它:

co_await asio::post(bind_executor(pstrand, asio::use_awaitable));

参见:何时必须通过io_context来boost::asio::spawn?(c++)或boost::asio::bind_executor不在链中执行,以了解更多关于它如何工作/为什么工作/何时工作的详细信息。

它实际上解释了get_associated_executor如何使用默认值,因此它可能解释了如何显式发布到执行器似乎没有在这里工作(我还没有检查use_awaitable的相关执行器实现)

这是我的看法:

Live On Compiler Explorer

#include <boost/asio.hpp>
#include <boost/asio/experimental/as_single.hpp>
#include <boost/bind/bind.hpp>
#include <coroutine>
#include <iostream>
#include <thread>
inline void tout(auto const& msg) {
static std::mutex mx;
std::lock_guard   lk(mx);
static const std::hash<std::thread::id> h{};
std::cout << "T" << (h(std::this_thread::get_id()) % 100) << " " << msg
<< std::endl;
}
namespace asio = boost::asio;
asio::awaitable<void> mainCo(asio::io_context& appIO,
asio::io_context& prodIO) {
auto to_app = bind_executor(make_strand(appIO), asio::use_awaitable);
auto to_prod = bind_executor(make_strand(prodIO), asio::use_awaitable);
tout("MC on APPIO");
co_await asio::post(to_prod); tout("MC on PRODIO");
co_await asio::post(to_app);  tout("MC on APPIO");
co_await asio::post(to_prod); tout("MC on PRODIO");
co_await asio::post(to_prod); tout("MC on PRODIO");
co_await asio::post(to_app);  tout("MC on APPIO");
}
int main() {
asio::io_context prodIO, appIO;
auto             prodWork = asio::make_work_guard(prodIO);
std::thread prodThread{[&prodIO] {
tout("ProdThread run start");
prodIO.run(); // if this call is removed the mainCo is stuck as
// expected
tout("ProdThread run done");
}};
asio::co_spawn(appIO, mainCo(appIO, prodIO), asio::detached);
tout("MainThread run start");
appIO.run();
tout("MainThread run done");
prodWork.reset();
prodThread.join();
}

打印。

T49 ProdThread run start
T31 MainThread run start
T31 MC on APPIO
T49 MC on PRODIO
T31 MC on APPIO
T49 MC on PRODIO
T49 MC on PRODIO
T31 MC on APPIO
T31 MainThread run done
T49 ProdThread run done

奖金我建议传递执行器,而不是执行上下文引用。它更干净更灵活:https://godbolt.org/z/Tr54vf8PM

然后用一个简单的thread_pool执行上下文替换prodIO + thread变得很简单。它消除了对工作保护的需要,也修复了缺失的异常处理:https://godbolt.org/z/a3GT61qdh

最新更新