Skip to content

Commit 3ce3c0f

Browse files
committed
Update README
1 parent 5cba660 commit 3ce3c0f

1 file changed

Lines changed: 93 additions & 29 deletions

File tree

README.md

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
# Dispatch Queue
2-
Dispatch Queue / Thread Pool implementation for C++11.
2+
Dispatch Queue / Thread Pool implementation for C++11 with built-in C++20 coroutine support.
33

44

55
## Features
6-
- No external dependencies: uses only C++11 threading functionality
7-
- `dispatch` method with API similar to [std::async](https://en.cppreference.com/w/cpp/thread/async): receives a functor and its arguments, returns a `std::future`
8-
- `dispatch_forget` for a "fire and forget" style call, which avoids the overhead of creating the `std::future`
9-
- Supports both synchronous (immediate) and asynchronous (threaded) execution.
10-
+ Asynchronous dispatch queues are also known as Thread Pools
11-
+ Synchronous dispatch queues are useful for multiplatform code that must work on platforms without thread support, for example WebAssembly on browsers that lack `SharedArrayBuffer` support
12-
- Single implementation file [src/dispatch_queue.cpp](src/dispatch_queue.cpp), easy to integrate in any project
6+
- No external dependencies: uses only the C++ STL
7+
- Supports both immediate and threaded execution modes:
8+
+ Threaded dispatch queues are also known as Thread Pools.
9+
In threaded mode it is safe to dispatch new tasks from any thread.
10+
+ In immediate mode tasks are executed immediately. Useful for multiplatform code that must work on platforms without thread support, for example WebAssembly on browsers that lack `SharedArrayBuffer` support.
11+
- Use `dispatch_queue.dispatch(f, args...)` to dispatch new tasks
12+
- Use `dispatch_queue.dispatch_main(f, args...)` to dispatch "main loop" tasks
13+
+ Users must call `dispatch_queue.main_loop()` manually where appropriate to run queued main loop tasks
14+
+ Useful for synchronizing state calculated in background tasks with the application's main loop
15+
- Returned `dispatch_queue::task<T>` from dispatch methods are similar to `std::shared_future`, with the following additions:
16+
+ Use `task.get_state()` to get whether task is pending, ready or failed with exception
17+
+ Use `task.then(f)` to add a continuation function that runs when task finishes
18+
+ Use `task.get_exception()` to get stored exception_ptr
19+
- Built-in C++20 coroutine support
20+
+ Use `dispatch_queue::task<T>` as the return value for your coroutines
21+
+ `co_await` other tasks to resume the coroutine as the task's continuation
22+
+ Use `co_await dispatch_queue.dispatch()` to continue coroutine in a dispatch queue's background loop
23+
+ Use `co_await dispatch_queue.dispatch_main()` to continue coroutine in a dispatch queue's main loop
24+
- Supports compiling with `-fno-exceptions` and `-fno-rtti`
25+
- Unified implementation file [src/dispatch_queue-one.cpp](src/dispatch_queue-one.cpp), easy to integrate in any project
1326

1427

1528
## Usage example
@@ -20,14 +33,14 @@ Dispatch Queue / Thread Pool implementation for C++11.
2033
// 1. Create a dispatch queue
2134
///////////////////////////////////////////////////////////
2235

23-
// Default constructed dispatch queues are synchronous.
24-
// They execute tasks immediately in the same thread that called `dispatch`.
25-
dispatch_queue::dispatch_queue synchronous_dispatcher;
26-
// Dispatch queues with 0 threads are also synchronous.
27-
dispatch_queue::dispatch_queue synchronous_dispatcher2(0);
36+
// Default constructed dispatch queues are immediate.
37+
// They execute tasks immediately in the call to `dispatch`.
38+
dispatch_queue::dispatch_queue immediate_dispatcher;
39+
// Dispatch queues with 0 threads are also immediate.
40+
dispatch_queue::dispatch_queue immediate_dispatcher2(0);
2841

29-
// A dispatch queue with 1 thread is called a serial queue:
30-
// it only runs a single task at a time.
42+
// A dispatch queue with 1 thread is a serial queue:
43+
// it runs a single task at a time in its background thread.
3144
dispatch_queue::dispatch_queue serial_dispatcher(1);
3245

3346
// A dispatch queue with more than 1 thread runs tasks concurrently.
@@ -42,24 +55,75 @@ dispatch_queue::dispatch_queue concurrent_dispatcher2(-1);
4255
// 2. Dispatch some tasks!
4356
///////////////////////////////////////////////////////////
4457

45-
// Use the returned future to get results or wait for completion.
46-
auto task = []() { return 42; };
47-
std::future<int> async_result = dispatcher.dispatch(task);
48-
assert(async_result.get() == 42);
58+
// Use the returned task to get results or wait for completion.
59+
auto work = []{ return 42; };
60+
dispatch_queue::task<int> task = dispatcher.dispatch(work);
61+
assert(task.get() == 42);
4962

5063
// Pass arguments to forward to task
51-
auto task2 = [](int value) { return value; };
52-
std::future<int> async_result2 = dispatcher.dispatch(task2, 2);
53-
assert(async_result2.get() == 2);
64+
auto work2 = [](int value) { return value; };
65+
dispatch_queue::task<int> task2 = dispatcher.dispatch(work2, 2);
66+
assert(task2.get() == 2);
67+
68+
// Use `then` for adding continuations
69+
dispatch_queue::task<void> continued_task = dispatcher.dispatch(work)
70+
// continuations receive the finished task
71+
.then([](dispatch_queue::task<int> task) {
72+
if (std::exception_ptr exception = task.get_exception()) {
73+
// task failed with an exception...
74+
std::rethrow_exception(exception);
75+
}
76+
else {
77+
// task succeeded!
78+
int result = task.get();
79+
return (float) result;
80+
}
81+
})
82+
// .then() return a new task, so you can chain continuations
83+
.then([&](dispatch_queue::task<float> task) {
84+
return dispatcher.dispatch(work2);
85+
})
86+
// .then() unwraps task<task<T>> if C++20 concepts are available
87+
.then([](dispatch_queue::task<int> task) {
88+
return;
89+
});
90+
continued_task.wait();
91+
92+
// Queue "main loop" tasks that will be executed by calling `main_loop()`
93+
dispatcher.dispatch_main([]{
94+
std::cout << "This will run inside the call to `main_loop`" << std::endl;
95+
});
96+
while (!ApplicationShouldExit()) {
97+
// Inside your application's main loop...
98+
dispatcher.main_loop();
99+
}
100+
101+
102+
///////////////////////////////////////////////////////////
103+
// 3. Built-in C++20 coroutine support
104+
///////////////////////////////////////////////////////////
105+
106+
// Use dispatch_queue::task<T> as return value for coroutines
107+
dispatch_queue::task<void> my_coro() {
108+
// co_await other tasks
109+
// coroutine becomes task's continuation via .then()
110+
co_await dispatcher.dispatch(some_work);
111+
do_something_after_some_work_finished();
112+
113+
// co_await .dispatch()
114+
// coroutine continues within dispatch queue
115+
co_await dispatcher.dispatch();
116+
do_something_in_background();
54117

55-
// For "fire and forget" calls, use `dispatch_forget`.
56-
// This avoids the overhead of creating `std::future`.
57-
dispatcher.dispatch_forget(task);
58-
dispatcher.dispatch_forget(task2, 2);
118+
// co_await .dispatch_main()
119+
// coroutine continues within dispatch queue's main loop
120+
co_await dispatcher.dispatch_main();
121+
do_something_in_main_loop();
122+
}
59123

60124

61125
///////////////////////////////////////////////////////////
62-
// 3. Check some stats
126+
// 4. Check some stats
63127
///////////////////////////////////////////////////////////
64128

65129
int dispatcher_thread_count = dispatcher.thread_count();
@@ -69,7 +133,7 @@ bool has_no_pending_tasks = dispatcher.empty();
69133

70134

71135
///////////////////////////////////////////////////////////
72-
// 4. Other operations
136+
// 5. Other operations
73137
///////////////////////////////////////////////////////////
74138

75139
// Cancel all pending tasks.
@@ -87,7 +151,7 @@ dispatcher.wait_until(std::chrono::system_clock::now() + std::chrono::seconds(5)
87151
## Using in CMake projects
88152
Add this project using `add_subdirectory` and link your target to `dispatch_queue`:
89153
```cmake
90-
add_subdirectory(path/to/dispatch_queue)
154+
add_subdirectory("path/to/dispatch_queue")
91155
target_link_libraries(my_target dispatch_queue)
92156
```
93157

0 commit comments

Comments
 (0)