C++ concurrency library with C++20 coroutines.
This project is a single header library.
Just include "DispatchQueue.h" in your project.
See the example file for more details: Sample code (test.cpp)
Note
The sample project is for Visual Studio 2022.
Requires a C++20 or later compiler.
-
Task
- Common coroutine function type
Task<int> getValue() { co_return 1; } int n = co_await getValue();
- Common coroutine function type
-
Generator
- Function type that yields multiple values without returning
Generator<int> gen(int n) { for (int i = 0; i < n; ++i) co_yield i; } for (auto g = gen(10); co_await g; ) { std::cout << "value: " << g.value(); }
- Function type that yields multiple values without returning
-
AsyncTask
- Asynchronous function type that runs on a specific dispatch queue.
- When it completes execution, it returns to the initial dispatch queue.
AsyncTask<int> getValueThread() { std::cout << "this_thread: " << std::this_thread::get_id(); co_return 0; } // Running on a background dispatch-queue and then returning to the original dispatch-queue. // NOTE: Returning to the same dispatch queue does not mean the same thread. // If the current dispatch queue owns multiple threads, it may return to a different thread than it was before the call. int value = getValueThread();
-
AsyncGenerator
- Asynchronous function type that runs on a specific dispatch queue.
- This function can yield multiple values to the caller without returning.
AsyncGenerator<int> gen(int n) { for (int i = 0; i < n; ++i) { std::cout << "callee-thread: " << std::this_thread::get_id(); co_yield i; } } for (auto g = gen(10); co_await g; ) { std::cout << "caller-thread: " << std::this_thread::get_id(); std::cout << "value: " << g.value(); }
-
DispatchQueue
- A queue that manages threads. A queue can have multiple threads.
- See the source code for a detailed implementation.
-
TaskGroup
- Group object for executing coroutine functions in parallel
-
AsyncTaskGroup
- Group object for executing coroutine functions in parallel
- Runs on a specific dispatch queue and returns to the original dispatch queue when it completes.
-
detachTask(AsyncTask)
-
detachTask(Task)
- Run the coroutine.
- This function can be called in a synchronous context.
- This is the entry function that spawns the asynchronous context.
-
asyncSleep(double)
- Suspends execution for a given amount of time, but does not actually sleep the thread.
-
asyncYield()
- Yield execution to another coroutine function.
-
async(TaskGroup)
-
async(AsyncTaskGroup)
- Run a TaskGroup with multiple coroutine functions.
// async function using background thread. Async<> func1() { co_await asyncSleep(10.0); // sleep 10s co_return; } // async function that returns int. Async<int> func2() { int n = co_await doSomething(); co_return n; } // a coroutine function that returns float. Task<float> func3() { co_return 1.0f; } // async generator to yield some values AsyncGenerator<int> asyncGen(int n) { for (int i = 0; i < n; ++i) co_yield i; } auto x = asyncGen(10); while (co_await x) { printf("yield: %d", x.value()); } // coroutine generator to yield INT values Generator<int> generator(int n) { for (int i = 0; i < n; ++i) co_yield i; } auto x = generator(3); while (co_await x) { printf("yield: %d", x.value()); } Async<> test() { co_await asyncSleep(10.0); co_return; } // run a new task in a background thread dispatchTask(test()); // run multiple new tasks in a background thread auto group = AsyncTaskGroup{ test(), test(), test() } co_await async(group); // run multiple new tasks that return values in a background thread Async<int> func1(int n) { co_return n * 2; } auto group = AsyncTaskGroup({ func1(3), func1(10), func1(20) }); auto x = co_await async(group); while (co_await x) { printf("yield: %d", x.value()); } // switching the running task thread DispatchQueue myQueue(3); // my queue with 3 threads Async<> work() { std::cout << std::this_thread::get_id() << std::end; co_await myQueue; // switching thread. std::cout << std::this_thread::get_id() << std::end; co_return; } - To use dispatchMain(), the main thread must be enabled.
std::atomic_flag stop_running; int main() { setDispatchQueueMainThread(); // Set the current thread as the main thread. // activate main loop auto& dq = DispatchQueue::main(); while (!stop_running.test()) { if (dq.dispatcher()->dispatch() == 0) { dq.dispatcher()->wait(); } }
- What if you can't control the main message loop?
- Use timer, run the following code once on the main thread.
TIMERPROC timerProc = [](HWND, UINT, UINT_PTR, DWORD) { auto& dq = dispatchMain(); while (dq.dispatcher()->dispatch()) {} }; setDispatchQueueMainThread(); SetTimer(nullptr, 0, USER_TIMER_MINIMUM, timerProc);- Install a timer in the main loop. With TIMEPROC, it is not affected by the modal-state of the Win32 message loop.
- Using Win32 Timer will result in a resolution of 10ms minimum, but we don't expect this to be a problem for asynchronous function execution in general.
- Use timer, run the following code once on the main thread.