A lightweight async coroutine library aimed at minimal dependencies, implemented using C++20 stackless coroutines
- Core has no external dependencies
- Built-in facilities for cancellation operations
- Structured concurrency
- Network support (TCP, UDP, UnixSocket, Async-GetAddrinfo)
- File I/O and pipe support
- Cross-platform (Windows, Linux)
- Simple single-threaded scheduler, easy to integrate with other frameworks like Qt
CI Name | Status |
---|---|
Windows | |
Linux | |
Coverage |
add_repositories("btk-repo https://github.com/Btk-Project/xmake-repo.git")
add_requires("ilias")
git submodule add https://github.com/BusyStudent/Ilias.git
Just copy all files from the include directory into your project
#include <ilias/platform.hpp>
#include <ilias/task.hpp>
auto main() -> int {
ilias::PlatformContext ctxt; // First, build a context for submitting tasks - this is thread_local, one per thread
ctxt.install();
// Available IO contexts include IocpContext, EpollContext, UringContext, QIoContext
// Simple executors without IO include MiniExecutor
// PlatformContext is a typedef that selects based on the current platform at compile time
auto task = []() -> ilias::Task<int> { // This is a coroutine, return type must be Task<T>
co_return 1;
};
auto result = task().wait(); // Create task and block waiting for completion
// Task<T> represents return value of type T, so T can be used below
assert(result == 1);
return 0;
}
For convenience, there's an ilias_main macro equivalent to the above:
#include <ilias/platform.hpp>
#include <ilias/task.hpp>
void ilias_main() {
co_return;
}
// or
int ilias_main() {
co_return 0;
}
// () supports both () and (int argc, char** argv) formats
// Return value supports void and int
Simple message sending:
#include <ilias/platform.hpp>
#include <ilias/task.hpp>
#include <ilias/net.hpp>
using ilias::TcpClient;
using ilias::IPEndpoint;
void ilias_main() { // Taking a shortcut :)
auto endpoint = IPEndpoint::fromString("127.0.0.1:8080").value();
auto client = (co_await TcpClient::make(endpoint.family)).value();
if (auto res = co_await client.connect(endpoint); !res) {
co_return;
}
// ilias::makeBuffer converts anything that can be converted to std::span<T> into std::span<const std::byte> or std::span<std::byte>
// read and write parameters are std::span<const std::byte> and std::span<std::byte> respectively
// read and write return an IoTask<size_t>
// IoTask<T, E = Error> is an alias for Task<Result<T, E>>, representing possible errors - see error handling section
std::string_view sv = "HELLO WORLD";
if (auto res = co_await client.write(ilias::makeBuffer(sv)); !res) {
co_return;
}
// Alternative way to construct TcpClient
auto ctxt = co_await ilias::currentIoContext();
TcpClient client2(ctxt, AF_INET);
}
Waiting for connections:
#include <ilias/sync/scope.hpp>
#include <ilias/platform.hpp>
#include <ilias/task.hpp>
#include <ilias/net.hpp>
using ilias::TcpListener;
using ilias::IPEndpoint;
using ilias::TaskScope;
void ilias_main() { // Taking shortcuts, not handling errors for demo, using value() directly :)
auto endpoint = IPEndpoint::fromString("127.0.0.1:8080").value();
auto scope = co_await TaskScope::make(); // TaskScope ensures all child coroutines complete when exiting
auto listener = (co_await TcpListener::make(endpoint.family)).value();
while (true) {
auto [client, _] = (co_await listener.accept()).value();
auto handle = scope.spawn(handleClient, std::move(client)); // Create child task in scope. Could use ilias::spawn directly but less safe
// handle can check completion or wait for completion. Losing handle is like detach
}
}
#include <ilias/platform/qt.hpp>
#include <ilias/http.hpp>
#include <QApplication>
auto main(int argc, char **argv) -> int {
QApplication app(argc, argv);
ilias::QIoContext ctxt; // Qt-integrated IoContext
// Code same as above - ready to use
}
Supports multiple synchronization methods: Channel, Mutex, whenAny, whenAll
- whenAny:
auto fn() -> Task<void> {
// Wait for either task to complete, other task gets cancellation notice and result discarded
auto [a, b] = co_await whenAny(taskA(), taskB());
if (a) { // taskA() completed first
}
if (b) { // taskB() completed first
}
}
- whenAll:
auto fn() -> Task<void> {
// Returns only when both complete
auto [a, b] = co_await whenAll(taskA(), taskB());
// use a & b
}
- Channel:
auto fn() -> Task<void> {
// Create channel
auto [sender, receiver] = mpmc::channel<int>(3); // 3 is capacity, blocks if send would exceed capacity. Default size_t::max() means unlimited
co_await sender.send(1);
auto val = co_await receiver.recv();
}
- liburing (optional, used by UringContext)
Backend | Progress | Description |
---|---|---|
epoll | Completed | |
IOCP | Completed | |
Qt | Completed | Qt Integration |
io_uring | Completed |