YOUR FIRST COROUTINE

来源:https://blog.panicsoftware.com/your-first-coroutine/

std::future<int> foo();

这是个普通函数,还是协程?这是由其具体实现决定的。

If any of those keywords occur in the function, then it becomes a coroutine.

  • co_await
  • co_return
  • co_yield

So the operator co_await is a unary operator, which takes the Awaitable object as its argument.

Why do we need to define additional types?

So the object used to communicate with the coroutine is the object of the coroutine’s return type.

我们使用协程的返回值,和协程进行沟通。

If our coroutine is able to suspend, we need to be able to somehow resume it, so our return type needs to have some resume() method.

协程可以暂停,所以协程的返回类型需要有 resume() 等接口。

How will the resumable object be created (there is no return statement)? Basically, the compiler generates some additional code for the coroutines. Every coroutine function is transformed by the compiler to the form similar to this:

1
2
3
4
5
6
7
8
9
10
Promise promise;
co_await promise.initial_suspend();
try {
// co-routine body
}
catch(...) {
promise.unhandled_exception();
}
final_suspend:
co_await promise.final_suspend();

What is more, the resumable object will be created before the initial_suspend through a call to the:

promise.get_return_object();

So what we need to do is to create the Promise type.

如何创建这个返回对象呢?由编译器生成的样板代码约束我们得实现 Promise 类型,它得包含以下接口:

  • resumable get_return_object() // 创建返回对象
  • auto initial_suspend()
  • auto final_suspend()
  • void return_void()
  • void unhandled_exception()

Unfortunately, we are not yet ready to define functions of our first promise type. To operate on the coroutine, we need to have some sort of handle to the coroutine, which we will manage. There already is a built-in object for this exact purpose.

如何实现上述接口呢?我们需要某种句柄来管理协程。

The coroutine_handle

The coroutine_handle is an object, that refers to the coroutine’s dynamically allocated state.

很关键的类型,TODO 稍后整理

Deeper into promise type.

Handling co_return

First of all, if you use co_return keyword without any expression on its right side (or void expression), the compiler generates:

promise.return_void(); goto final_suspend;

But if there is some non-void expression, the compiler generates a slightly different code:

promise.return_value(expression); goto final_suspend;

Making the use of co_yield operator

Now to implement our promise_type correctly, we need to know what kind of code compiler generates when it encounters the co_yield keyword. And the following snippet shows precisely this.

co_await promise.yield_value(expression);

So what is missing is the yield_value member function. Also worth to note is, that no co_await keyword appears, but we will be talking about the co_await keyword later on. For now, the knowledge, that co_await + suspend_always suspends the coroutine is enough.

Summary

So as you can see, the coroutines are hard to learn, after all, it’s still not everything about the coroutines since we only touched Awaitables and Awaiter objects. Fortunately because of the level of difficulty the coroutines the coroutines are very flexible.

幸运的是,由于协程的难度,协程非常灵活。

The good thing is that the whole complicated part of this feature is not to be known by the regular C++ developer. In fact, the regular C++ developer should know how to write the body of the coroutine, but not coroutine objects themselves.

好消息是常规C ++开发人员不知道此功能的整个复杂部分。实际上,常规的C ++开发人员应该知道如何编写协程的主体,而不是协程对象本身。

Developers definitely should use already defined coroutines objects from the standard library (which later will come into the standard) or third-party libraries (like cppcoro).