如果 C++20 协程既不调用也不存储完成处理程序,会发生什么?

问题描述 投票:0回答:1

概述

当我使用 BoostAsio 的 CompletionToken 工具时,我设计从最终调用的

completion_handler
生成
CompletionToken

但是我对

completion_handler
既没有被调用也没有被存储的行为感兴趣。在这种情况下,
completion_handler
就被简单地销毁了。

我已经使用

use_awaitable
CompletionToken 测试了 C++20 协程的行为。然后我观察到有趣的行为。在
co_await
completion_handler
销毁路径执行之后,堆栈被展开并且
ioc.run()
int main()
中完成。在此过程中,不会捕获任何异常。 这是预期的行为还是未定义的行为?如果这是预期的行为,那么该文档在哪里?目前还没找到。

代码

#include <iostream>
#include <boost/asio.hpp>

namespace as = boost::asio;


template <typename CompletionToken>
auto func(bool call, CompletionToken&& token) {
    auto init = 
        []
        (
            auto completion_handler,
            bool call
        ) {
            if (call) {
                std::move(completion_handler)();
            }
            else {
                // What is happend if completion_handler is neither invoked nor stored ?
            }
        };

    return as::async_initiate<
        CompletionToken,
        void()
    >(
        init,
        token,
        call
    );
}

struct trace {
    trace()  { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    ~trace() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

as::awaitable<void> proc1() {
    trace t; // destructed correctly
    try {
        {
            std::cout << "before call=true" << std::endl;
            co_await func(true, as::use_awaitable);
            std::cout << "after  call=true" << std::endl;
            
        }
        {
            std::cout << "before call=false" << std::endl;
            co_await func(false, as::use_awaitable);
            // the following part is never executed
            std::cout << "after  call=false" << std::endl;
        }
    }
    catch (...) {
        std::cout << "caught exception" << std::endl;
    }
    std::cout << "co_return" << std::endl;
    co_return;
}

as::awaitable<void> proc2() {
    for (int i = 0; i != 2; ++i) {
        std::cout << "before proc1" << std::endl;
        co_await proc1();
        std::cout << "after  proc1" << std::endl;
    }
}

int main() {
    as::io_context ioc;
    as::co_spawn(ioc.get_executor(), proc2, as::detached);
    ioc.run();
    std::cout << "finish" << std::endl;
}

输出

before proc1
trace::trace()
before call=true
after  call=true
before call=false
trace::~trace()
finish

godbolt 链接:https://godbolt.org/z/3dzoYban6

注意

在实践中,我不使用“可能不可调用”令牌。我使用基于错误代码的方法。

template <typename CompletionToken>
auto func(bool call, CompletionToken&& token) {
    auto init = 
        []
        (
            auto completion_handler,
            bool call
        ) {
            if (call) {
                std::move(completion_handler)(boost::system::error_code{});
            }
            else {
                // invoke with error code
                std::move(completion_handler)(
                    boost::system::errc::make_error_code(
                        boost::system::errc::operation_canceled 
                    )
                );
            }
        };

    return as::async_initiate<
        CompletionToken,
        void(boost::syste::error_code const&)
    >(
        init,
        token,
        call
    );
}
c++ boost c++20 coroutine asio
1个回答
0
投票

completion_handler 类型为

auto init = [](auto completion_handler, bool should_complete) {
    boost::asio::detail::awaitable_handler<boost::asio::any_io_executor> ch = std::move(completion_handler);
    if (should_complete) {
        std::move(ch)();
    } else {
        // What is happend if completion_handler is neither invoked nor stored ?
    }
};

awaitable_handler
间接继承自
awaitable_thread
,它在销毁时确实负责堆栈展开:

  // Clean up with a last ditch effort to ensure the thread is unwound within
  // the context of the executor.
  ~awaitable_thread()
  {
    if (bottom_of_stack_.valid())
    {
      // Coroutine "stack unwinding" must be performed through the executor.
      auto* bottom_frame = bottom_of_stack_.frame_;
      (post)(bottom_frame->u_.executor_,
          [a = std::move(bottom_of_stack_)]() mutable
          {
            (void)awaitable<awaitable_thread_entry_point, Executor>(
                std::move(a));
          });
    }
  }

通常,所有权会从一个处理程序转移到另一个处理程序,但在这里,没有任何东西再引用它,因此它不再存在。

虽然这是实现细节,但它使得如果 coro 永远无法恢复,则引用计数将变为零。另外,一路上有一些不错的代码注释,您可能会发现它们很有洞察力,例如

impl/awaitable.hpp
开始于

// An awaitable_thread represents a thread-of-execution that is composed of one
// or more "stack frames", with each frame represented by an awaitable_frame.
// All execution occurs in the context of the awaitable_thread's executor. An
// awaitable_thread continues to "pump" the stack frames by repeatedly resuming
// the top stack frame until the stack is empty, or until ownership of the
// stack is transferred to another awaitable_thread object.
//
//                +------------------------------------+
//                | top_of_stack_                      |
//                |                                    V
// +--------------+---+                            +-----------------+
// |                  |                            |                 |
// | awaitable_thread |<---------------------------+ awaitable_frame |
// |                  |           attached_thread_ |                 |
// +--------------+---+           (Set only when   +---+-------------+
//                |               frames are being     |
//                |               actively pumped      | caller_
//                |               by a thread, and     |
//                |               then only for        V
//                |               the top frame.)  +-----------------+
//                |                                |                 |
//                |                                | awaitable_frame |
//                |                                |                 |
//                |                                +---+-------------+
//                |                                    |
//                |                                    | caller_
//                |                                    :
//                |                                    :
//                |                                    |
//                |                                    V
//                |                                +-----------------+
//                | bottom_of_stack_               |                 |
//                +------------------------------->| awaitable_frame |
//                                                 |                 |
//                                                 +-----------------+
© www.soinside.com 2019 - 2024. All rights reserved.