3

In C#, nested await is supported like below example. The compiler will stitch the resumption of the code together (i.e. return count in method 1 will invoke int count= in outer layer and then method3 and console.ReadKey()). However I found it hard to this in c++ coroutine framework.

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);  
    }  
} 

In C++ coroutine framework (below is my failed attempt in C++),how do I resume the code after the call to "co_await Foo2()"? It seems we need to chain the coroutine handles together problematically and calls it after the deepest co_await resume. But how to do that? Below is the output.

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

Below is the code with key question on how to do nested resume in comment.

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));
}
10
  • Right now, you have no code that resumes the coroutine that was saved in await_suspend. You probably want to resume that coroutine in final_suspend. Commented Jun 14, 2021 at 3:04
  • @Raymond Chen, I did try call handle.resume but it caused crash. Commented Jun 14, 2021 at 3:07
  • Your code uses h_ for two different things - get() assumes it holds the coroutine being returned, but await_suspend stores the coroutine that is awaiting. Commented Jun 14, 2021 at 13:07
  • @RaymondChen, I simplified the code to make the key question clearer. After Foo2 is resumed (displayed "resume in Foo2\n" as expected), how can I resume the caller frame's await from there? I want to be able to hit the code "resume in Foo1". At Foo2's final_suspect, I can only obtain the coroutine handle for the current Foo2's coroutine; I can't obtain its parent's. So Foo1's resumption code (ie "resume in Foo1\n") can't be hit. Does C++ coroutine framework provide away to walk back the coroutines? Commented Jun 15, 2021 at 1:28
  • The return_type's await_suspend method needs to save the parent's coroutine handle in the promise, so the promise can resume it when it hits its final_suspend. Commented Jun 15, 2021 at 2:38

2 Answers 2

3

The await_suspend of the return_type needs to give the coroutine handle to the promise_type so that the promise_type can resume the awaiter during final_suspend. See the comments marked with !!.

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
}

This shows how to connect the coroutine with its parent. Note however that there are still a number of other issues with this code, but I didn't try to address them since they weren't part of the question.

Sign up to request clarification or add additional context in comments.

Comments

0

One way I made the nesting to work by chaining the promise object inside await_suspend. result:

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

Code:

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));
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.