Skip to content

Commit 8dee189

Browse files
authored
Async: some ideas for simplifying the content (#550)
* Simplify the async-await slide * Shorten futures and move it up * Add a page on Tokio
1 parent 8e121bc commit 8dee189

File tree

6 files changed

+80
-86
lines changed

6 files changed

+80
-86
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@
231231

232232
- [Async](async.md)
233233
- [async/await](async/async-await.md)
234-
- [Async Blocks](async/async-blocks.md)
235234
- [Futures](async/futures.md)
236235
- [Runtimes](async/runtimes.md)
237-
- [Tasks](async/tasks.md)ures](async/futures.md)
236+
- [Tokio](async/runtimes/tokio.md)
237+
- [Tasks](async/tasks.md)
238238
- [Async Channels](async/channels.md)
239239
- [Futures Control Flow](async/control-flow.md)
240240
- [Daemon](async/control-flow/daemon.md)

src/async/async-await.md

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,47 @@
33
At a high level, async Rust code looks very much like "normal" sequential code:
44

55
```rust,editable,compile_fail
6-
use tokio::time;
6+
use futures::executor::block_on;
77
88
async fn count_to(count: i32) {
99
for i in 1..=count {
10-
println!("Count in task: {i}!");
11-
time::sleep(time::Duration::from_millis(5)).await;
10+
println!("Count is: {i}!");
1211
}
1312
}
1413
15-
#[tokio::main]
16-
async fn main() {
17-
tokio::spawn(count_to(10));
14+
async fn async_main(count: i32) {
15+
let future = count_to(count);
16+
future.await;
17+
}
1818
19-
for i in 1..5 {
20-
println!("Main task: {i}");
21-
time::sleep(time::Duration::from_millis(5)).await;
22-
}
19+
fn main() {
20+
let future = async_main(10);
21+
block_on(future);
2322
}
2423
```
2524

2625
<details>
2726

2827
Key points:
2928

30-
* Tokio is one of several async runtimes available for Rust.
31-
32-
* The function is decorated with the "async" keyword to indicate that it is async. The
33-
`tokio::main` macro invocation is a convenience to wrap the `main` function as a task.
34-
35-
* The `spawn` function creates a new, concurrent "task", just like spawning a thread.
29+
* Note that this is a simplified example to show the syntax. There is no long
30+
running operation or any real concurrency in it!
3631

37-
* Whenever a task would block, we add an `.await` which returns control to the runtime until the
38-
blocking operation is ready to proceed.
32+
* What is the return type of an async call?
33+
* Change `let future = async_main(10);` to `let future: () = async_main(10);`
34+
to see the type.
3935

40-
Further exploration:
36+
* The "async" keyword is syntactic sugar. The compiler replaces the return type.
4137

42-
* Why does `count_to` not (usually) get to 10? This is an example of async cancellation.
43-
`tokio::spawn` returns a handle which can be awaited to wait until it finishes.
38+
* You cannot make `main` async, without additional instructions to the compiler
39+
on how to use the returned future.
4440

45-
* Try `count_to(10).await` instead of spawning.
41+
* You need an executor to run async code. `block_on` blocks the current thread
42+
until the provided future has run to completion.
4643

47-
* Try importing `tokio::join` and using it to join multiple handles.
44+
* `.await` asynchronously waits for the completion of another operation. Unlike
45+
`block_on`, `.await` doesn't block the current thread.
4846

49-
Note that the Rust playground does not allow network connections, so examples like making HTTP
50-
requests are not possible.
47+
* `.await` can only be used inside an `async` block.
5148

5249
</details>

src/async/async-blocks.md

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/async/control-flow/daemon.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ async fn main() {
2828

2929
<details>
3030

31-
* It is good practice to make your deamons exit because some other blocking task might depend on them. Which would prevent your main thread from ever closing. You can use a `oneshot` channel to signal the task to terminate. You can also use the `ctrl+c` signal handler from `tokio` as an interrupt signal.
31+
* An async block is similar to a closure, but does not take any arguments. Its
32+
return value is a Future, similar to `async fn`.
33+
34+
* It is good practice to make your deamons exit because some other blocking
35+
task might depend on them. Which would prevent your main thread from ever
36+
closing. You can use a `oneshot` channel to signal the task to terminate. You
37+
can also use the `ctrl+c` signal handler from `tokio` as an interrupt signal.
3238

3339
</details>

src/async/futures.md

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,5 @@
11
# Futures
22

3-
What is the type of an async operation?
4-
5-
```rust,editable,compile_fail
6-
use tokio::time;
7-
8-
async fn count_to(count: i32) -> i32 {
9-
for i in 1..=count {
10-
println!("Count in task: {i}!");
11-
time::sleep(time::Duration::from_millis(5)).await;
12-
}
13-
count
14-
}
15-
16-
#[tokio::main]
17-
async fn main() {
18-
println!("Final count is: {}!", count_to(13).await);
19-
20-
// Uncomment the following line to see the return type of the async call.
21-
// let _: () = count_to(13);
22-
23-
}
24-
```
25-
263
[Future](https://doc.rust-lang.org/nightly/src/core/future/future.rs.html#37)
274
is a trait, implemented by objects that represent an operation that may not be
285
complete yet. A future can be polled, and `poll` returns either

src/async/runtimes/tokio.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Tokio
2+
3+
4+
Tokio provides:
5+
6+
* A multi-threaded runtime for executing asynchronous code.
7+
* An asynchronous version of the standard library.
8+
* A large ecosystem of libraries.
9+
10+
```rust,editable,compile_fail
11+
use tokio::time;
12+
13+
async fn count_to(count: i32) {
14+
for i in 1..=count {
15+
println!("Count in task: {i}!");
16+
time::sleep(time::Duration::from_millis(5)).await;
17+
}
18+
}
19+
20+
#[tokio::main]
21+
async fn main() {
22+
tokio::spawn(count_to(10));
23+
24+
for i in 1..5 {
25+
println!("Main task: {i}");
26+
time::sleep(time::Duration::from_millis(5)).await;
27+
}
28+
}
29+
```
30+
31+
<details>
32+
33+
* With the `tokio::main` macro we can now make `main` async.
34+
35+
* The `spawn` function creates a new, concurrent "task".
36+
37+
* Note: `spawn` takes a `Future`, you don't call `.await` on `count_to`.
38+
39+
**Further exploration:**
40+
41+
* Why does `count_to` not (usually) get to 10? This is an example of async
42+
cancellation. `tokio::spawn` returns a handle which can be awaited to wait
43+
until it finishes.
44+
45+
* Try `count_to(10).await` instead of spawning.
46+
47+
* Try importing `tokio::join` and using it to join multiple handles.
48+
49+
</details>

0 commit comments

Comments
 (0)