Home > Software engineering >  C Coroutine - When, How to use?
C Coroutine - When, How to use?

Time:02-18

As a novice C programmer who is very new to the concept of coroutines, I am trying to study and utilize the feature. Although there is explanation of coroutine here: Coroutine concept

(From the image posted by Izana)

CodePudding user response:

The word "coroutine" in this context is somewhat overloaded.

The general programming concept called a "coroutine" is what is described in the question you're referring to. C 20 added a language feature called "coroutines". While C 20's coroutines are somewhat similar to the programming concept, they're not all that similar.

At the ground level, both concepts are built on the ability of a function (or call stack of functions) to halt its execution and transfer control of execution to someone else. This is done with the expectation that control will eventually be given back to the function which has surrendered execution for the time being.

Where C coroutines diverge from the general concept is in their limitations and designed application.

co_await <expr> as a language construct does the following (in very broad strokes). It asks the expression <expr> if it has a result value to provide at the present time. If it does have a result, then the expression extracts the value and execution in the current function continues as normal.

If the expression cannot be resolved at the present time (perhaps because <expr> is waiting on an external resource or asynchronous process or something), then the current function suspends its execution and returns control to the function that called it. The coroutine also attaches itself to the <expr> object such that, once <expr> has the value, it should resume the coroutine's execution with said value. This resumption may or may not happen on the current thread.

So we see the pattern of C 20 coroutines. Control on the current thread returns to the caller, but resumption of the coroutine is determined by the nature of the value being co_awaited on. The caller gets an object that represents the future value the coroutine will produce but has not yet. The caller can wait on it to be ready or go do something else. It may also be able to itself co_await on the future value, creating a chain of coroutines to be resumed once a value is computed.

We also see the primary limitation: suspension applies only to the immediate function. You cannot suspend an entire stack of function calls unless each one of them individually does their own co_awaits.

C coroutines are a complex dance between 3 parties: the expression being awaited on, the code doing the awaiting, and the caller of the coroutine. Using co_yield essentially removes one of these three parties. Namely, the yielded expression is not expected to be involved. It's just a value which is going to be dumped to the caller. So yielding coroutines only involve the coroutine function and the caller. Yielding C coroutines a bit closer to the conceptual idea of "coroutines".

Using a yielding coroutine to serve a number of values to the caller is generally called a "generator". How "simple" this makes your code depends on your generator framework (ie: the coroutine return type and its associated coroutine machinery). But good generator frameworks can expose range interfaces to the generation, allowing you to apply C 20 ranges to them and do all sorts of interesting compositions.

CodePudding user response:

coroutine makes asynchronous programing more readable.

if there is no coroutine, we will use callback in asynchronous programing.

void callback(int data1, int data2)
{
    // do something with data1, data2 after async op
    // ...
}

void async_op(std::function<void()> callback)
{
    // do some async operation
}

int main()
{
    // do something
    int data1;
    int data2;
    async_op(std::bind(callback, data1, data2));
    return 0;
}

if there is a lot of callback, the code will very hard to read. if we use coroutine the code will be

#include <coroutine>
#include <functional>

struct promise;

struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = struct promise;
};

struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};

struct awaitable
{
    bool await_ready() { return false; }
    void await_suspend(std::coroutine_handle<promise> h)
    {
        func();
    }
    void await_resume() { }

    std::function<void()> func;
};

void async_op()
{
    // do some async operation
}

coroutine callasync()
{
    // do somethine
    int data1;
    int data2;
    co_await awaitable(async_op);
    // do something with data1, data2 after async op
    // ...
}

int main()
{
    callasync();
    return 0;
}
  • Related