挂起的协程句柄是否应始终在程序结束前销毁?
Should the suspended coroutine handle be always destroyed before program ends?
看看这个简化的例子:
std::coroutine_handle<> logger;
const char* next_msg = nullptr;
void log(const char* msg)
{
next_msg = msg;
if (logger) logger.resume();
}
struct wait_msg {
bool await_ready() {
return next_msg != nullptr;
}
void await_suspend(std::coroutine_handle<> h) {
logger = h;
}
auto await_resume() {
const char* msg = next_msg;
next_msg = nullptr;
return msg;
}
};
struct procedure {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type handle;
struct promise_type {
procedure get_return_object() {
return { handle_type::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
procedure log_messages()
{
int c = 1;
for (;;) {
const char* msg = co_await wait_msg{};
std::cout << c++ << ": " << msg << std::endl;
}
}
假设我在初始点或最终点没有暂停,协程在 co_await 暂停,是否应该在程序结束前销毁协程句柄?
或者:可以从 main
中删除第 p.handle.destroy();
行吗?
int main() {
log("Hello World!");
auto p = log_messages();
log("Hello World, again!");
log("Hello World, and again!");
// is the next line needed?
p.handle.destroy();
}
查看完整内容 demo。
有问题的代码的先前版本 here。
Should the suspended coroutine handle be always destroyed before program ends?
没有,有些情况下destroy()
是不需要的,甚至是不可能的。
是,在问题的代码中,协程句柄上需要destroy()
。
standard 中的这句话解释了事情:
The coroutine state is destroyed when control flows off the end of the
coroutine or the destroy member function
([coroutine.handle.resumption]) of a coroutine handle
([coroutine.handle]) that refers to the coroutine is invoked.
因为这个协程永远不会跳出无限循环 (for(;;)
) - 它必须通过它的句柄销毁。
procedure log_messages()
{
int c = 1;
for (;;) {
const char* msg = co_await wait_msg{};
std::cout << c++ << ": " << msg << std::endl;
}
}
但是,可以稍微修改代码 - 例如 - returns nullptr 当没有更多行要打印时,并在 nullptr 上中断循环:
procedure log_messages()
{
int c = 1;
for (;;) {
const char* msg = co_await wait_msg{};
if (!msg) break; // !!!
std::cout << c++ << ": " << msg << std::endl;
}
}
在这种情况下 - 不需要甚至不可能使用 destroy(),因为它会导致 UB。
这是因为 destroy()
只能在挂起的协程上调用,而我们在问题代码的最后一点没有挂起:
struct promise_type {
//...
std::suspend_never final_suspend() noexcept { return {}; }
//...
};
};
看看这个简化的例子:
std::coroutine_handle<> logger;
const char* next_msg = nullptr;
void log(const char* msg)
{
next_msg = msg;
if (logger) logger.resume();
}
struct wait_msg {
bool await_ready() {
return next_msg != nullptr;
}
void await_suspend(std::coroutine_handle<> h) {
logger = h;
}
auto await_resume() {
const char* msg = next_msg;
next_msg = nullptr;
return msg;
}
};
struct procedure {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type handle;
struct promise_type {
procedure get_return_object() {
return { handle_type::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
procedure log_messages()
{
int c = 1;
for (;;) {
const char* msg = co_await wait_msg{};
std::cout << c++ << ": " << msg << std::endl;
}
}
假设我在初始点或最终点没有暂停,协程在 co_await 暂停,是否应该在程序结束前销毁协程句柄?
或者:可以从 main
中删除第 p.handle.destroy();
行吗?
int main() {
log("Hello World!");
auto p = log_messages();
log("Hello World, again!");
log("Hello World, and again!");
// is the next line needed?
p.handle.destroy();
}
查看完整内容 demo。
有问题的代码的先前版本 here。
Should the suspended coroutine handle be always destroyed before program ends?
没有,有些情况下destroy()
是不需要的,甚至是不可能的。
是,在问题的代码中,协程句柄上需要destroy()
。
standard 中的这句话解释了事情:
The coroutine state is destroyed when control flows off the end of the coroutine or the destroy member function ([coroutine.handle.resumption]) of a coroutine handle ([coroutine.handle]) that refers to the coroutine is invoked.
因为这个协程永远不会跳出无限循环 (for(;;)
) - 它必须通过它的句柄销毁。
procedure log_messages()
{
int c = 1;
for (;;) {
const char* msg = co_await wait_msg{};
std::cout << c++ << ": " << msg << std::endl;
}
}
但是,可以稍微修改代码 - 例如 - returns nullptr 当没有更多行要打印时,并在 nullptr 上中断循环:
procedure log_messages()
{
int c = 1;
for (;;) {
const char* msg = co_await wait_msg{};
if (!msg) break; // !!!
std::cout << c++ << ": " << msg << std::endl;
}
}
在这种情况下 - 不需要甚至不可能使用 destroy(),因为它会导致 UB。
这是因为 destroy()
只能在挂起的协程上调用,而我们在问题代码的最后一点没有挂起:
struct promise_type {
//...
std::suspend_never final_suspend() noexcept { return {}; }
//...
};
};