class Example : public coro::IContext
{
public:
QFuture<QString> myCoroutine(int milliseconds)
{
co_await coro::Delay(milliseconds);
// "Example" is considered a coroutine context object.
// If it gets destroyed while awaiting, we'll never get here.
// Local variables (if any) are guaranteed to be destroyed
// before destroying the context object.
co_return QStringLiteral("finished");
}
};
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
// Create a context and schedule its deletion.
Example* context = new coro::Context<Example>;
int deleteAfter = QRandomGenerator::global()->bounded(500, 1500);
QTimer::singleShot(deleteAfter, [context] { delete context; });
// Start a coroutine and attach continuations.
context->myCoroutine(1000)
.onCanceled([] { return QStringLiteral("canceled"); })
.then([](QString result) { qInfo() << result; });
QTimer::singleShot(2000, [] { QCoreApplication::quit(); });
return QCoreApplication::exec();
}This example and also a more elaborated TCP example can be found it the "examples" subfolder.
-
Everything here is about
co_await/co_returnonly. The keywordco_yieldis not covered. -
A coroutine is executed within the thread where it was started. In general, Qt event loop is required to run continuations.
-
Return object of a coroutine function is
QFuture. -
The primary thing that can be "co_awaited" is also
QFuture; the result ofco_awaitis normally the outcome ofQFuture(which can be a resulting value or an exception). Cancellation is treated differently than the normal outcome. -
Cancellation of the "co_awaited"
QFutureresults in the following:- the
co_awaitoperator never returns control; - local variables of the "co_awaiting" coroutine are destroyed by destroying the coroutine frame;
- the "co_awaiting" coroutine (i. e. its
QFuturereturn object) is canceled as well.
- the
-
A "co_awaiting" coroutine is canceled in the same way when its context object (if any) is destroyed.
-
A coroutine has a context object if the following conditions are met:
- the coroutine function is a non-static member function of some class;
- the instance of that class (the context object)
is created using the
coro::Contexttemplate (see the above example), which implies that the class satisfies thecoro::ContextBaseconcept (it's enough to inherit from thecoro::IContextconvenience interface).
-
Definitions of
coro::ContextBaseandcoro::IContextare as follows:
namespace coro
{
template <typename T>
concept ContextBase = requires(const T& object)
{
requires std::is_abstract_v<T>;
requires std::has_virtual_destructor_v<T>;
{ object.getCoroutineContext() } -> std::same_as<const void*>;
};
class IContext
{
public:
virtual ~IContext() = default;
virtual const void* getCoroutineContext() const = 0;
};
}
To use the library, add the subfolder "coro" to the header search path.
The library doesn't contain any separately compiled code and depends only on Qt::Core.
- Qt 6
- A compiler supporting C++20 coroutines and concepts (e.g. GCC 11 or MSVC 2022)