Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/hongtae/corodispatchqueue

Simple Concurrency Library with Coroutine Dispatch Queue
https://github.com/hongtae/corodispatchqueue

async async-await asynchronous asynchronous-programming concurrency concurrency-library coroutines cpp-coroutines cpp-programming cpp20 dispatchqueue multithreading parallelism single-header-library threadpool

Last synced: 17 days ago
JSON representation

Simple Concurrency Library with Coroutine Dispatch Queue

Awesome Lists containing this project

README

        

# Cpp Coroutine Dispatch Queue
C++ concurrency library with C++20 coroutines.

This project is a single header library.
Just include ["DispatchQueue.h"](DispatchQueue.h) in your project.

See the example file for more details: [Sample code (test.cpp)](test.cpp)

> [!NOTE]
> The sample project is for Visual Studio 2022.

**Requires a C++20 or later compiler.**

## Types
- Task
- Common coroutine function type
```
Task getValue() {
co_return 1;
}

int n = co_await getValue();
```

- Generator
- Function type that yields multiple values without returning
```
Generator 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();
}
```
- AsyncTask
- Asynchronous function type that runs on a specific dispatch queue.
- When it completes execution, it returns to the initial dispatch queue.
```
AsyncTask 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 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.

## Functions
- 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.

## Usage
```
// async function using background thread.
Async<> func1() {
co_await asyncSleep(10.0); // sleep 10s
co_return;
}

// async function that returns int.
Async func2() {
int n = co_await doSomething();
co_return n;
}

// a coroutine function that returns float.
Task func3() {
co_return 1.0f;
}
```

```
// async generator to yield some values
AsyncGenerator 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 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 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;
}
```
### Registering main thread
- 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();
}
}
```

## Tips & Tricks
#### Using Win32 Main-thread as MainQueue
- 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.