【问题标题】:nested co_await/coroutine similar to C# nested await style嵌套的 co_await/coroutine 类似于 C# 的嵌套等待风格
【发布时间】:2021-06-13 23:37:23
【问题描述】:

在 C# 中,支持嵌套等待,如下例所示。编译器会将代码的恢复拼接在一起(即方法 1 中的返回计数将在外层调用 int count=,然后调用方法 3 和 console.ReadKey())。但是我发现在 c++ 协程框架中很难做到这一点。

class Program  
{  
    static void Main(string[] args)  
    {  
        callMethod();  
        Console.ReadKey();  
    }  
  
    public static async void callMethod()  
    {  
        Task<int> task = Method1();  
        Method2();  
        int count = await task;  
        Method3(count);  
    }  
  
    public static async Task<int> Method1()  
    {  
        int count = 0;  
        await Task.Run(() =>  
        {  
            for (int i = 0; i < 100; i++)  
            {  
                Console.WriteLine(" Method 1");  
                count += 1;  
            }  
        });  
        return count;  
    }  
  
    public static void Method2()  
    {  
        for (int i = 0; i < 25; i++)  
        {  
            Console.WriteLine(" Method 2");  
        }  
    }  
  
    public static void Method3(int count)  
    {  
        Console.WriteLine("Total count is " + count);  
    }  
} 

在 C++ 协程框架中(以下是我在 C++ 中的失败尝试),如何在调用“co_await Foo2()”后恢复代码?看来我们需要有问题地将协程句柄链接在一起,并在最深的 co_await 恢复之后调用它。但是怎么做呢?下面是输出。

140717656352576 Promise created
Send back a return_type
Created a return_type object
140717656352576 Started the coroutine, don't stop now!
140717656352576 enter Foo1
140717656352576 Promise created
Send back a return_type
Created a return_type object
140717656352576 Started the coroutine, don't stop now!
140717656352576 enter Foo2
140717656352576 await_suspend
140717656352576 await_suspend in return_type
140717656352576 After Foo1 
140717656348416 in Run
140717656348416 await_resume
140717656348416 resume in Foo2
140717656348416 Finished the coro --> **anyway I can call Foo1's handle to resume here?**
140717656348416 Promise died
return_type gone

下面是关于如何在评论中进行嵌套简历的关键问题的代码。

void run(std::coroutine_handle<> h)
{
  std::cout<<std::this_thread::get_id()<<" "<<"in Run\n";
  std::this_thread::sleep_for (std::chrono::seconds(5));
  h.resume();
}

struct MyObj {
  MyObj():v_(0){}
  MyObj(int v):v_(v){}
  int get() { return v_; }
  int v_;
};

struct return_type {

    return_type() {
        std::cout << "Created a return_type object"<<std::endl;
    }

    ~return_type() {
        std::cout << "return_type gone" << std::endl;
    }

    struct promise_type {
        promise_type() {
            std::cout<<std::this_thread::get_id() <<" Promise created" << std::endl;
        }

        ~promise_type() {
            std::cout<<std::this_thread::get_id() << " Promise died" << std::endl;
        }

        auto get_return_object() {
            std::cout << "Send back a return_type" <<std::endl;
            return return_type();
        }

        auto initial_suspend() {
            std::cout<<std::this_thread::get_id() <<" Started the coroutine, don't stop now!" << std::endl;
            return std::suspend_never{};
        }

        auto final_suspend() {
            std::cout<<std::this_thread::get_id() << " Finished the coro" << std::endl;
            return std::suspend_never{};
        }
        void unhandled_exception() {
            std::exit(1);
        }
    };

    constexpr bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<promise_type> h) {
      std::cout<<std::this_thread::get_id()<<" "<<"await_suspend in return_type\n";
    }

    void await_resume() const noexcept { 
      std::cout<<std::this_thread::get_id()<<" "<<"await_resume in resume_type\n"; 
    }
};

struct Awaitable {
  constexpr bool await_ready() const noexcept { return false; }
  void await_suspend(std::coroutine_handle<> h) 
  {
    std::cout<<std::this_thread::get_id()<<" "<<"await_suspend\n";
    std::thread t(run, h);
    t.detach();
  }

  void await_resume() const noexcept { 
    std::cout<<std::this_thread::get_id()<<" "<<"await_resume\n"; 
  }
};

return_type Foo2()
{ 
  std::cout<<std::this_thread::get_id()<<" "<<"enter Foo2\n";
  Awaitable a;
  co_await a;
  std::cout<<std::this_thread::get_id()<<" "<<"resume in Foo2\n";
  **// This is where promise_type::final_suspend() is called
  // Naturally, I'd want to call previous (Foo1)'s handle to resume 
  // but I have no way of doing so.** 
}

return_type Foo1()
{
  std::cout<<std::this_thread::get_id()<<" "<<"enter Foo1\n";
  co_await Foo2();
  std::cout<<std::this_thread::get_id()<<" resume in Foo1\n";
}

int main() {
  auto r = Foo1();
  std::cout<<std::this_thread::get_id()<<" After Foo1 \n";;
  std::this_thread::sleep_for (std::chrono::seconds(10));
}

【问题讨论】:

  • 现在,您没有恢复保存在await_suspend 中的协程的代码。您可能希望在 final_suspend 中恢复该协程。
  • @Raymond Chen,我确实尝试调用 handle.resume 但它导致了崩溃。
  • 您的代码将h_ 用于两件不同的事情——get() 假定它保存了正在返回的协程,但await_suspend 存储了正在等待的协程。
  • @RaymondChen,我简化了代码以使关键问题更清晰。 Foo2 恢复后(按预期显示“在 Foo2\n 中恢复”),如何从那里恢复调用者框架的等待?我希望能够点击代码“在 Foo1 中恢复”。在Foo2的final_suspect,我只能获取当前Foo2的协程的协程句柄;我无法获得其父母的。所以 Foo1 的恢复代码(即“在 Foo1\n 中恢复”)不能被命中。 C++ 协程框架是否提供回退协程?
  • return_typeawait_suspend方法需要将父协程句柄保存在promise中,这样promise在遇到final_suspend时可以恢复它。

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


【解决方案1】:

return_typeawait_suspend 需要将协程句柄交给promise_type,以便promise_type 可以在final_suspend 期间恢复等待者。查看标有!!的cmets。

struct return_type {
    std::coroutine_handle<>& waiting_; // !! To communicate with the promise_type

    return_type(std::coroutine_handle<>& waiting) : waiting_(waiting) { // !! save it
        std::cout << "Created a return_type object"<<std::endl;
    }

    ~return_type() {
        std::cout << "return_type gone" << std::endl;
    }

    struct promise_type {
        promise_type() {
            std::cout<<std::this_thread::get_id() <<" Promise created" << std::endl;
        }

        ~promise_type() {
            std::cout<<std::this_thread::get_id() << " Promise died" << std::endl;
        }

        auto get_return_object() {
            std::cout << "Send back a return_type" <<std::endl;
            return return_type(waiting_); // !! To communicate with the return_type
        }

        auto initial_suspend() {
            std::cout<<std::this_thread::get_id() <<" Started the coroutine, don't stop now!" << std::endl;
            return std::suspend_never{};
        }

        auto final_suspend() noexcept { // !! you forgot "noexcept"
            std::cout<<std::this_thread::get_id() << " Finished the coro" << std::endl;
            if (waiting_) waiting_.resume(); // !! resume anybody who is awaiting
            return std::suspend_never{};
        }
        void unhandled_exception() {
            std::exit(1);
        }

        void return_void() {} // !! you forgot this
        std::coroutine_handle<> waiting_; // !! the awaiting coroutine

    };

    constexpr bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<promise_type> h) {
      std::cout<<std::this_thread::get_id()<<" "<<"await_suspend in return_type\n";
      waiting_ = h; // !! tell the promise_type who to resume when finished
    }

    void await_resume() const noexcept { 
      std::cout<<std::this_thread::get_id()<<" "<<"await_resume in resume_type\n"; 
    }
};

return_type Foo2()
{ 
  std::cout<<std::this_thread::get_id()<<" "<<"enter Foo2\n";
  Awaitable a;
  co_await a;
  std::cout<<std::this_thread::get_id()<<" "<<"resume in Foo2\n";
  co_return; // !! this ends Foo2() and resumes Foo1
}

这显示了如何将协程与其父级连接起来。但请注意,此代码仍有许多其他问题,但我没有尝试解决这些问题,因为它们不是问题的一部分。

【讨论】:

    【解决方案2】:

    我通过在 await_suspend 中链接 promise 对象来使嵌套工作。 结果:

    140053703153472 enter Foo0
    140053703153472 enter Foo1
    140053703153472 enter Foo2
    140053703153472 main thread wait for 10 seconds
    140053703149312 async operation completed
    140053703149312 resume in Foo2
    140053703149312 resume in Foo1
    140053703149312 resume in Foo0
    

    代码:

    void run(std::coroutine_handle<> h)
    {
      std::cout<<std::this_thread::get_id()<<" "<<"in Run\n";
      std::this_thread::sleep_for (std::chrono::seconds(5));
      h.resume();
    }
    
    struct return_type {
    
      struct promise_type {
        
        auto get_return_object() {
          return return_type(std::coroutine_handle<promise_type>::from_promise(*this));
        }
    
        auto initial_suspend() {
          return std::suspend_never{};
        }
    
        auto final_suspend() {
          if (prev_ != nullptr) {
            auto hh = std::coroutine_handle<promise_type>::from_promise(*prev_);
            hh.resume();
          }
    
          return std::suspend_never{};
        }
            
        void unhandled_exception() {
          std::exit(1);
        }
    
        void return_void() {}
    
        promise_type* prev_ = nullptr;
      };
        
      return_type(bool async) : async_(async) {}
    
      return_type(std::coroutine_handle<promise_type> h) : handle_{h} {
      }
    
      constexpr bool await_ready() const noexcept { return false; }
    
      void await_suspend(std::coroutine_handle<promise_type> h) {
        if (async_) {
          std::thread t([&promise = h.promise()](){
            std::this_thread::sleep_for (std::chrono::seconds(1));
            std::cout<<std::this_thread::get_id()<<" "<<"async operation completed\n";
            auto h = std::coroutine_handle<promise_type>::from_promise(promise);
            h.resume();
          });
            
          t.detach();
        } else {
          handle_.promise().prev_ = &h.promise();
        }
      }
    
      void await_resume() const noexcept { 
      }
    
      std::coroutine_handle<promise_type> handle_;
      bool async_ = false;
    };
    
    return_type Foo2()
    { 
      std::cout<<std::this_thread::get_id()<<" "<<"enter Foo2\n";
      return_type r(true);
      co_await r;
      std::cout<<std::this_thread::get_id()<<" "<<"resume in Foo2\n";
      co_return;
    }
    
    return_type Foo1()
    {
      std::cout<<std::this_thread::get_id()<<" "<<"enter Foo1\n";
      co_await Foo2();
      std::cout<<std::this_thread::get_id()<<" resume in Foo1\n";
      co_return;
    }
    
    return_type Foo0()
    {
      std::cout<<std::this_thread::get_id()<<" "<<"enter Foo0\n";
      co_await Foo1();
      std::cout<<std::this_thread::get_id()<<" resume in Foo0\n";
      co_return;
    }
    
    int main() {
      auto r = Foo0();
      std::cout<<std::this_thread::get_id()<<" main thread wait for 10 seconds\n";;
      std::this_thread::sleep_for (std::chrono::seconds(10));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-21
      • 2018-06-04
      • 1970-01-01
      • 1970-01-01
      • 2022-08-24
      • 2015-08-15
      • 2018-05-05
      • 1970-01-01
      相关资源
      最近更新 更多