英文:
asio: awaitable operator|| don't return when timer expires
问题
以下是您提供的代码的翻译部分:
从一个较大的代码库中提取的附加代码具有我无法解释的行为。
问题出现在“run”函数中,我希望等待由async_initiate返回的可等待完成的最长时间。
由async_initiate启动的异步操作从不完成(在此示例中是因为我从未调用处理程序,在实际程序中是因为它正在等待网络数据包),但是即使计时器到期,协程也会停在co_await上。
asio版本是与boost 1.81.0一起提供的
// 此函数仅存在以保持io_context繁忙
asio::awaitable<void> busy() {
auto exec = co_await asio::this_coro::executor;
using asio::ip::udp;
auto socket = udp::socket(exec, udp::endpoint(udp::v4(), 40000));
uint8_t msg[1024];
std::cout << "reading from socket...\n";
co_await socket.async_receive(asio::buffer(msg), asio::use_awaitable);
}
std::optional<asio::any_completion_handler<void(int)>> stored;
asio::awaitable<void> run() {
std::cout << "run() called\n";
auto exec = co_await asio::this_coro::executor;
asio::steady_timer timer{exec, std::chrono::seconds(2)};
auto initiate = [&]([[maybe_unused]] asio::any_completion_handler<void(int)> handler) {
// 故意不调用处理程序
//
// 仅出于此示例的缘故,将其移至“stored”以排除处理程序析构的任何副作用
stored = std::move(handler);
};
co_await (asio::async_initiate<const asio::use_awaitable_t<>, void(int)>(initiate, asio::use_awaitable)
|| timer.async_wait(asio::use_awaitable));
std::cout << "done\n";
}
int main() {
asio::io_context io;
asio::co_spawn(io, busy, asio::detached);
asio::co_spawn(io, run, asio::detached);
io.run();
}
请注意,我已经将HTML实体(如<和>)转换回了正常的C++代码标记。如果您需要更多的帮助或有其他问题,请随时提出。
英文:
The attached code, extracted from a larger codebase, has behavior that I cannot
explain.
The problem is in the run
function where I would like to wait up to a maximum
time that the awaitable returned by async_initiate completes.
The async operation initiated by async_initiate
never completes (in this
example because I never call the handler, in the real program because it is
waiting for a network packet), but the coroutine is stuck on the co_await even
if the timer expires.
The asio version is the one shipped with boost 1.81.0
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace asio::experimental::awaitable_operators;
// this function exists only to keep the io_context busy
asio::awaitable<void> busy() {
auto exec = co_await asio::this_coro::executor;
using asio::ip::udp;
auto socket = udp::socket(exec, udp::endpoint(udp::v4(), 40000));
uint8_t msg[1024];
std::cout << "reading from socket...\n";
co_await socket.async_receive(asio::buffer(msg), asio::use_awaitable);
}
std::optional<asio::any_completion_handler<void(int)>> stored;
asio::awaitable<void> run() {
std::cout << "run() called\n";
auto exec = co_await asio::this_coro::executor;
asio::steady_timer timer{exec, std::chrono::seconds(2)};
auto initiate = [&]([[maybe_unused]] asio::any_completion_handler<void(int)> handler) {
// don't call the handler on purpose
//
// move it on `stored` only for the sake of this example, to rule out
// any side-effect of the handler destructor
stored = std::move(handler);
};
co_await (asio::async_initiate<const asio::use_awaitable_t<>, void(int)>(initiate, asio::use_awaitable)
|| timer.async_wait(asio::use_awaitable));
std::cout << "done\n";
}
int main() {
asio::io_context io;
asio::co_spawn(io, busy, asio::detached);
asio::co_spawn(io, run, asio::detached);
io.run();
}
I know that operator|| is waiting for "a success" but this should not be the cause of the problem because the time completes (or should complete) without an error
答案1
得分: 4
以下是您请求的内容的翻译:
由
async_initiate
启动的异步操作永远不会完成(在此示例中,因为我从未调用处理程序,
如果不完成,您也无法观察到它以operation_aborted
完成。
在实际程序中,因为它正在等待网络数据包,但是协程却在
co_await
上卡住,即使定时器已经过期。
让我们来测试一下:
#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace asio::experimental::awaitable_operators;
using namespace std::chrono_literals;
asio::awaitable<void> busy() {
auto exec = co_await asio::this_coro::executor;
using asio::ip::udp;
try {
while (true) {
auto socket = udp::socket(exec, udp::endpoint(udp::v4(), 40000));
uint8_t msg[1024];
std::cout << "reading from socket..." << std::endl;
auto n = co_await socket.async_receive(asio::buffer(msg), asio::use_awaitable);
std::cout << "received " << n << " bytes" << std::endl;
}
} catch (boost::system::system_error const& se) {
auto const& ec = se.code();
std::cout << "receive failed: " << ec.message() << " at " << ec.location() << std::endl;
}
}
asio::awaitable<void> run() {
std::cout << "run() called\n";
auto exec = co_await asio::this_coro::executor;
asio::steady_timer timer{exec, 2s};
co_await (busy() || timer.async_wait(asio::use_awaitable));
std::cout << "done\n";
}
int main() {
asio::io_context io;
asio::co_spawn(io, run, asio::detached);
io.run();
}
与广告一样运作。如果您喜欢避免异常处理,您可以使用as_tuple
或redirect_error
:
asio::awaitable<void> busy() {
auto exec = co_await asio::this_coro::executor;
for (error_code ec; !ec.failed();) {
auto socket = asio::ip::udp::socket{exec, {{}, 40000}};
uint8_t msg[1024];
std::cout << "reading from socket..." << std::endl;
auto n = co_await socket.async_receive(asio::buffer(msg), redirect_error(asio::use_awaitable, ec));
std::cout << "received " << n << " bytes (" << ec.message() << ")" << std::endl;
}
}
您自己的异步初始化?
您自己的IO对象/操作可能不支持取消。如果您想要,可以在cancellation_slot
上实现它。让我们还展示如何使用工作保护来保持执行上下文的活动:
template <typename Token>
auto async_stuff(Token token) {
auto initiate = [](auto handler) {
auto cs = asio::get_associated_cancellation_slot(handler);
auto work = make_work_guard(asio::get_associated_executor(handler));
cs.assign([work, h = std::move(handler)](asio::cancellation_type type) mutable {
using ct = asio::cancellation_type;
if (ct::none != (type & ct::terminal))
std::cout << "cancellation_type terminal" << std::endl;
if (ct::none != (type & ct::partial))
std::cout << "cancellation_type partial" << std::endl;
if (ct::none != (type & ct::total))
std::cout << "cancellation_type total" << std::endl;
work.reset();
std::move(h)(asio::error::operation_aborted, -1);
});
};
return asio::async_initiate<Token, void(error_code, int)>(initiate, token);
}
您可以像这样绑定您的取消槽:
int main() {
asio::thread_pool io(1);
asio::cancellation_signal cancel;
auto token = asio::bind_cancellation_slot(cancel.slot(), asio::use_awaitable);
asio::co_spawn(io, async_stuff(token), asio::detached);
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
cancel.emit(asio::cancellation_type::terminal);
io.join();
}
实验性操作符使用parallel_group
,它在底层使用取消槽。
英文:
> The async operation initiated by async_initiate never completes (in this example because I never call the handler,
If you don't complete, you can't witness that it completes with operation_aborted
either.
> in the real program because it is waiting for a network packet), but the coroutine is stuck on the co_await even if the timer expires.
Let's put that to the test:
#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace asio::experimental::awaitable_operators;
using namespace std::chrono_literals;
asio::awaitable<void> busy() {
auto exec = co_await asio::this_coro::executor;
using asio::ip::udp;
try {
while (true) {
auto socket = udp::socket(exec, udp::endpoint(udp::v4(), 40000));
uint8_t msg[1024];
std::cout << "reading from socket..." << std::endl;
auto n = co_await socket.async_receive(asio::buffer(msg), asio::use_awaitable);
std::cout << "received " << n << " bytes" << std::endl;
}
} catch (boost::system::system_error const& se) {
auto const& ec = se.code();
std::cout << "receive failed: " << ec.message() << " at " << ec.location() << std::endl;
}
}
asio::awaitable<void> run() {
std::cout << "run() called\n";
auto exec = co_await asio::this_coro::executor;
asio::steady_timer timer{exec, 2s};
co_await (busy() || timer.async_wait(asio::use_awaitable));
std::cout << "done\n";
}
int main() {
asio::io_context io;
asio::co_spawn(io, run, asio::detached);
io.run();
}
Works as advertised. If you prefer to avoid exception handling, you can use as_tuple
or redirect_error
:
asio::awaitable<void> busy() {
auto exec = co_await asio::this_coro::executor;
for (error_code ec; !ec.failed();) {
auto socket = asio::ip::udp::socket{exec, {{}, 40'000}};
uint8_t msg[1024];
std::cout << "reading from socket..." << std::endl;
auto n = co_await socket.async_receive(asio::buffer(msg), redirect_error(asio::use_awaitable, ec));
std::cout << "received " << n << " bytes (" << ec.message() << ")" << std::endl;
}
}
Your Own Async Initiatiation?
Your own IO objects/operations may not support cancellation. If you want, implement is on cancellation_slot
. Let's also show how to use a work guard to keep the execution context alive:
template <typename Token>
auto async_stuff(Token token) {
auto initiate = [](auto handler) {
auto cs = asio::get_associated_cancellation_slot(handler);
auto work = make_work_guard(asio::get_associated_executor(handler));
cs.assign([work, h = std::move(handler)](asio::cancellation_type type) mutable {
using ct = asio::cancellation_type;
if (ct::none != (type & ct::terminal))
std::cout << "cancellation_type terminal" << std::endl;
if (ct::none != (type & ct::partial))
std::cout << "cancellation_type partial" << std::endl;
if (ct::none != (type & ct::total))
std::cout << "cancellation_type total" << std::endl;
work.reset();
std::move(h)(asio::error::operation_aborted, -1);
});
};
return asio::async_initiate<Token, void(error_code, int)>(initiate, token);
}
You can bind your cancellation slot like this:
int main() {
asio::thread_pool io(1);
asio::cancellation_signal cancel;
auto token = asio::bind_cancellation_slot(cancel.slot(), asio::use_awaitable);
asio::co_spawn(io, async_stuff(token), asio::detached);
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
cancel.emit(asio::cancellation_type::terminal);
io.join();
}
The experimental operators use parallel_group
which use cancellation slots under the hood.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论