Skip to content

Commit 6e95702

Browse files
committed
add guide on how to use async correctly
1 parent 1d62bd6 commit 6e95702

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

tutorials/migrate-v5.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,50 @@ listener.cancel();
159159

160160
When rewriting your own code that used `Task`, you can forget about checking for cancellation and leverage the power of coroutines - your task can be cancelled at any `co_await` point.
161161

162+
## Async in general
163+
164+
This section is useful for those who have done multithreading in mods, whether via creating threads manually or using `geode::Task`. The new async system provides a much easier way for concurrency, as a central system shared between Geode and mods. Specifically, there are two types of jobs that can run in parallel: **Async Tasks** and **Blocking Tasks**.
165+
166+
Async tasks are the most common ones, and they are useful for pretty much any concurrent tasks that aren't CPU heavy and don't block. For more examples on what you can do with them, see the [Arc README](https://github.com/dankmeme01/arc), but here's a small example of an asynchronous worker task that you previously could've only implemented with an `std::thread`:
167+
```cpp
168+
auto [tx, rx] = arc::mpsc::channel<int>();
169+
170+
auto handle = async::runtime().spawn([](auto rx) -> arc::Future<> {
171+
while (auto result = co_await rx.recv()) {
172+
log::debug("Got value: {}", std::move(result).unwrap());
173+
co_await arc::sleep(asp::Duration::fromMillis(100));
174+
}
175+
}(std::move(rx)));
176+
177+
// Then, in regular, sync code you can use the mpsc channel to communicate with the worker
178+
(void) tx.trySend(1);
179+
```
180+
181+
There are a few things you should strictly avoid doing inside async tasks:
182+
* Blocking the thread (for networking use Arc sockets, for sleep use `arc::sleep`, for other blocks see below)
183+
* Using `std::mutex` (or other sync primitives) across await boundaries - prefer to use `arc::Mutex` or ensure you unlock the mutex before doing `co_await`
184+
* Running CPU intensive tasks or IO that isn't networking: decoding images, writing large files; these should be done as blocking tasks
185+
186+
For when blocking is required, you can use the **blocking task pool** (and you should prefer doing that over using your own thread pool!):
187+
```cpp
188+
auto handle = async::runtime().spawnBlocking<uint64_t>([] {
189+
// simulate some expensive calculation
190+
uint64_t x = 1;
191+
for (int i = 0; i < 1024; i++) {
192+
x = x * (x + i);
193+
}
194+
return x;
195+
});
196+
197+
// The task now runs in parallel, and it will not be stopped if you discard the hnadle
198+
// To get the output value, you have two ways:
199+
200+
// 1. await if inside an async task
201+
uint64_t value = co_await handle;
202+
// 2. block (only if NOT inside the async runtime!)
203+
uint64_t value = handle.blockOn();
204+
```
205+
162206
## `fmt::localtime`
163207

164208
We updated our `fmt` dependency to v12, which removed the `fmt::localtime` function. Now Geode provides a drop-in replacement for it:

0 commit comments

Comments
 (0)