【问题标题】:Can a coroutine be destroyed during evaluation of an inner await expression?在评估内部等待表达式期间可以销毁协程吗?
【发布时间】:2020-05-12 04:17:00
【问题描述】:

根据标准,协程只有在被挂起时才会被销毁[dcl.fct.def.coroutine]

如果为未挂起的协程调用destroy,则程序具有未定义的行为。

在协程 A 中评估 await 表达式期间,假设此事件序列对应于 [expr.await]/5.1

  1. 协程 A 被挂起,因为 await-ready 为假;
  2. 在将控制权交给协程 A 调用者之前,协程 B 被恢复;
  3. 一旦 B 也被挂起,控制流将返回到协程 A 调用者。

可以在协程B恢复后挂起之前销毁协程A吗?

示例代码:

#include <coroutine>

using namespace std;

struct task
    {
    struct promise_type;

    using handle_type = coroutine_handle<promise_type>;

    struct promise_type
        {
        handle_type resumer = nullptr; 
        auto
        get_return_object(){
            return task{handle_type::from_promise(*this)};
            }

        auto 
        initial_suspend(){
            return suspend_always {};
            }

        auto 
        unhandled_exception(){}

        auto
        final_suspend(){
            return suspend_always{};
            }

        void 
        return_void() {}

        };


   handle_type handle;

   void await_resume(){
       handle.resume();
   }

   auto
   await_suspend(handle_type h){
       handle.promise().resumer = h;
       return handle;
   }
   auto
   await_ready(){
       return false;
   }

    };


int main(){

  task coroutine_B = [&coroutine_B]() ->task
            {
            coroutine_B.handle.promise().resumer.destroy();
            co_return;
            }();//coroutine B supended at initial suspend

  task coroutine_A = [&coroutine_B]() ->task 
            {
            co_await coroutine_B;//set coroutine_B resumer to coroutine_A handle
                                 //then resume coroutine_B.
            }();//coroutine A supended at initial suspend.

  coroutine_A.handle.resume();//execute co_await coroutine_B;
                              //is this UB?

}

可以看到here,代码编译并运行似乎没有任何问题。

另一方面,这个显然等效的版本 here 崩溃了。

【问题讨论】:

    标签: c++ language-lawyer coroutine c++20


    【解决方案1】:

    dcl.fct.def.coroutine#11 中的标准说

    协程状态在以下情况下被销毁...调用了引用协程的协程句柄的destroy成员函数...如果为未挂起的协程调用destroy,则程序未定义行为。

    在您的示例中,destroy 是从 coroutine_B 调用的 coroutine_A 的协程句柄,由于 co_await 而被挂起。所以destroy成员函数是为一个挂起的协程调用的,所以里面没有未定义的行为。

    确实,Clang、GCC、MSVC 都可以正常编译和执行代码:https://gcc.godbolt.org/z/zxePvsvx1

    另一方面,这个显然等效的版本在这里崩溃了。

    这里的问题是 coroutine_B.resumer 在 GCC 中包含 nullptr 地址,所以 coroutine_B.resumer.destroy() 是未定义的行为。演示:https://gcc.godbolt.org/z/T8aa45ez1

    【讨论】:

      猜你喜欢
      • 2011-08-08
      • 1970-01-01
      • 2012-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-27
      • 2013-06-18
      相关资源
      最近更新 更多