Skip to content

Commit 2db1f14

Browse files
authored
Modifications to the async section (#556)
* Modifications to the async section * Remove the "Daemon" slide, as it largely duplicates the "Tasks" slide. The introduction to the "Control Flow" section mentions tasks as a kind of control flow. * Reorganize the structure in SUMMARY.md to correspond to the directory structure. * Simplify the "Pin" and "Blocking the Executor" slides with steps in the speaker notes to demonstrate / fix the issues. * Rename "join_all" to "Join". * Simplify some code samples to shorten them, and to print output rather than asserting. * Clarify speaker notes and include more "Try.." suggestions. * Be consistent about where `async` blocks are introduced (in the "Tasks" slide). * Explain `join` and `select` in prose. * Fix formatting of section-header slides.
1 parent 8dee189 commit 2db1f14

14 files changed

+202
-226
lines changed

src/SUMMARY.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,21 +229,20 @@
229229

230230
----
231231

232-
- [Async](async.md)
232+
- [Async Basics](async.md)
233233
- [async/await](async/async-await.md)
234234
- [Futures](async/futures.md)
235235
- [Runtimes](async/runtimes.md)
236236
- [Tokio](async/runtimes/tokio.md)
237237
- [Tasks](async/tasks.md)
238238
- [Async Channels](async/channels.md)
239-
- [Futures Control Flow](async/control-flow.md)
240-
- [Daemon](async/control-flow/daemon.md)
241-
- [Join](async/control-flow/join_all.md)
242-
- [Select](async/control-flow/select.md)
243-
- [Pitfalls](async/pitfalls.md)
244-
- [Blocking the Executor](async/pitfalls/blocking-executor.md)
245-
- [Pin](async/pitfalls/pin.md)
246-
- [Async Traits](async/pitfalls/async-traits.md)
239+
- [Control Flow](async/control-flow.md)
240+
- [Join](async/control-flow/join.md)
241+
- [Select](async/control-flow/select.md)
242+
- [Pitfalls](async/pitfalls.md)
243+
- [Blocking the Executor](async/pitfalls/blocking-executor.md)
244+
- [Pin](async/pitfalls/pin.md)
245+
- [Async Traits](async/pitfalls/async-traits.md)
247246
- [Exercises](exercises/day-4/async.md)
248247

249248
# Final Words

src/async/async-await.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@ async fn count_to(count: i32) {
1212
}
1313
1414
async fn async_main(count: i32) {
15-
let future = count_to(count);
16-
future.await;
15+
count_to(count).await;
1716
}
1817
1918
fn main() {
20-
let future = async_main(10);
21-
block_on(future);
19+
block_on(async_main(10));
2220
}
2321
```
2422

@@ -30,10 +28,10 @@ Key points:
3028
running operation or any real concurrency in it!
3129

3230
* 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.
31+
* Use `let future: () = async_main(10);` in `main` to see the type.
3532

36-
* The "async" keyword is syntactic sugar. The compiler replaces the return type.
33+
* The "async" keyword is syntactic sugar. The compiler replaces the return type
34+
with a future.
3735

3836
* You cannot make `main` async, without additional instructions to the compiler
3937
on how to use the returned future.
@@ -44,6 +42,7 @@ Key points:
4442
* `.await` asynchronously waits for the completion of another operation. Unlike
4543
`block_on`, `.await` doesn't block the current thread.
4644

47-
* `.await` can only be used inside an `async` block.
45+
* `.await` can only be used inside an `async` function (or block; these are
46+
introduced later).
4847

4948
</details>

src/async/channels.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Async Channels
22

3-
Multiple Channels crates have support for `async`/`await`. For instance `tokio` channels:
3+
Several crates have support for `async`/`await`. For instance `tokio` channels:
44

55
```rust,editable,compile_fail
66
use tokio::sync::mpsc::{self, Receiver};
@@ -12,6 +12,8 @@ async fn ping_handler(mut input: Receiver<()>) {
1212
count += 1;
1313
println!("Received {count} pings so far.");
1414
}
15+
16+
println!("ping_handler complete");
1517
}
1618
1719
#[tokio::main]
@@ -35,9 +37,11 @@ async fn main() {
3537
* Overall, the interface is similar to the `sync` channels as seen in the
3638
[morning class](concurrency/channels.md).
3739

38-
* The `Flume` crate has channels that implement both `sync` and `async` `send`
39-
and `recv`. This can be convenient for complex application with both IO and
40-
heavy CPU processing tasks.
40+
* Try removing the `std::mem::drop` call. What happens? Why?
41+
42+
* The [Flume](https://docs.rs/flume/latest/flume/) crate has channels that
43+
implement both `sync` and `async` `send` and `recv`. This can be convenient
44+
for complex applications with both IO and heavy CPU processing tasks.
4145

4246
* What makes working with `async` channels preferable is the ability to combine
4347
them with other `future`s to combine them and create complex control flow.

src/async/control-flow.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# Futures Control Flow
22

33
Futures can be combined together to produce concurrent compute flow graphs. We
4-
will cover multiple common operations:
4+
have already seen tasks, that function as independent threads of execution.
55

6-
----
7-
8-
- [Daemon](control-flow/daemon.md)
9-
- [Join](control-flow/join_all.md)
6+
- [Join](control-flow/join.md)
107
- [Select](control-flow/select.md)

src/async/control-flow/daemon.md

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

src/async/control-flow/join_all.md renamed to src/async/control-flow/join.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
# join_all
1+
# Join
22

3-
Futures can be combined together to produce concurrent compute flow graphs.
4-
5-
## Run a group of futures concurrently until they all resolve: `join_all`
6-
7-
### Equivalents:
8-
9-
- JS: `Promise.all`
10-
- Python: `asyncio.gather`
3+
A join operation waits until all of a set of futures are ready, and
4+
returns a collection of their results. This is similar to `Promise.all` in
5+
JavaScript or `asyncio.gather` in Python.
116

127
```rust,editable,compile_fail
138
use anyhow::Result;
@@ -38,11 +33,19 @@ async fn main() {
3833

3934
<details>
4035

41-
* `join_all` should soon be stabilized as part of the standard library in `std::future`.
42-
* For multiple futures of disjoint types, you can use `join!` but you must know how many futures you will have at compile time.
43-
* You can also combine `join_all` with `join!` for instance to join all requests to an http service as well as a database query.
44-
* The risk of `join` is that one of the future could never resolve, this would cause your program to stall.
45-
* Try adding a timeout to the future.
36+
Copy this example into your prepared `src/main.rs` and run it from there.
37+
38+
* For multiple futures of disjoint types, you can use `std::future::join!` but
39+
you must know how many futures you will have at compile time. This is
40+
currently in the `futures` crate, soon to be stabilised in `std::future`.
41+
42+
* The risk of `join` is that one of the futures may never resolve, this would
43+
cause your program to stall.
44+
45+
* You can also combine `join_all` with `join!` for instance to join all requests
46+
to an http service as well as a database query.
47+
48+
* Try adding a timeout to the future, using `futures::join!`.
4649

4750
</details>
4851

src/async/control-flow/select.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Select
22

3-
## Run multiple futures concurrently until the first one resolves
3+
A select operation waits until any of a set of futures is ready, and responds to
4+
that future's result. In JavaScript, this is similar to `Promise.race`. In
5+
Python, it compares to `asyncio.wait(task_set,
6+
return_when=asyncio.FIRST_COMPLETED)`.
47

5-
### Equivalents:
6-
7-
- JS: `Promise.race`
8-
- Python: `asyncio.new_event_loop().run_until_complete(asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED))`
8+
This is usually a macro, similar to match, with each arm of the form `pattern =
9+
future => statement`. When the future is ready, the statement is executed with the
10+
variable bound to the future's result.
911

1012
```rust,editable,compile_fail
1113
use tokio::sync::mpsc::{self, Receiver};
@@ -32,32 +34,42 @@ async fn main() {
3234
let (cat_sender, cat_receiver) = mpsc::channel(32);
3335
let (dog_sender, dog_receiver) = mpsc::channel(32);
3436
tokio::spawn(async move {
35-
sleep(Duration::from_secs(10)).await;
37+
sleep(Duration::from_millis(500)).await;
3638
cat_sender
3739
.send(String::from("Felix"))
3840
.await
3941
.expect("Failed to send cat.");
4042
});
4143
tokio::spawn(async move {
42-
sleep(Duration::from_secs(5)).await;
44+
sleep(Duration::from_millis(50)).await;
4345
dog_sender
4446
.send(String::from("Rex"))
4547
.await
46-
.expect("Failed to send cat.");
48+
.expect("Failed to send dog.");
4749
});
4850
4951
let winner = first_animal_to_finish_race(cat_receiver, dog_receiver)
5052
.await
5153
.expect("Failed to receive winner");
5254
53-
assert_eq!(winner, Animal::Dog {name: String::from("Rex")});
55+
println!("Winner is {winner:?}");
5456
}
5557
```
5658

5759
<details>
5860

59-
* In this example, we have a race between a cat and a dog. `first_animal_to_finish_race` listens to both channels and will pick whichever arrives first. Since the dog takes 5 seconds, it wins against the cat that take 10 seconds.
60-
* You can use `oneshot` channels in this example as the channels are supposed to receive only one `send`.
61-
* You can try adding more contestants to the race and return a leaderboard. Also, you can add a deadline after which contestants get eliminated.
61+
* In this example, we have a race between a cat and a dog.
62+
`first_animal_to_finish_race` listens to both channels and will pick whichever
63+
arrives first. Since the dog takes 50ms, it wins against the cat that
64+
take 500ms seconds.
65+
66+
* You can use `oneshot` channels in this example as the channels are supposed to
67+
receive only one `send`.
68+
69+
* Try adding a deadline to the race, demonstrating selecting different sorts of
70+
futures.
71+
72+
* Note that `select!` consumes the futures it is given, and is easiest to use
73+
when every execution of `select!` creates new futures.
6274

6375
</details>

src/async/futures.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Futures
22

3-
[Future](https://doc.rust-lang.org/nightly/src/core/future/future.rs.html#37)
3+
[`Future`](https://doc.rust-lang.org/std/future/trait.Future.html)
44
is a trait, implemented by objects that represent an operation that may not be
5-
complete yet. A future can be polled, and `poll` returns either
6-
`Poll::Ready(result)` or `Poll::Pending`.
5+
complete yet. A future can be polled, and `poll` returns a
6+
[`Poll`](https://doc.rust-lang.org/std/task/enum.Poll.html).
77

88
```rust
99
use std::pin::Pin;
@@ -20,18 +20,17 @@ pub enum Poll<T> {
2020
}
2121
```
2222

23-
An async function returns an `impl Future`, and an async block evaluates to an
24-
`impl Future`. It's also possible (but uncommon) to implement `Future` for your
25-
own types. For example, the `JoinHandle` returned from `tokio::spawn` implements
26-
`Future` to allow joining to it.
23+
An async function returns an `impl Future`. It's also possible (but uncommon) to
24+
implement `Future` for your own types. For example, the `JoinHandle` returned
25+
from `tokio::spawn` implements `Future` to allow joining to it.
2726

28-
The `.await` keyword, applied to a Future, causes the current async function or
29-
block to pause until that Future is ready, and then evaluates to its output.
27+
The `.await` keyword, applied to a Future, causes the current async function to
28+
pause until that Future is ready, and then evaluates to its output.
3029

3130
<details>
3231

33-
* The `Future` and `Poll` types are conceptually quite simple, and implemented as
34-
such in `std::task`.
32+
* The `Future` and `Poll` types are implemented exactly as shown; click the
33+
links to show the implementations in the docs.
3534

3635
* We will not get to `Pin` and `Context`, as we will focus on writing async
3736
code, rather than building new async primitives. Briefly:

src/async/pitfalls.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
Async / await provides convenient and efficient abstraction for concurrent asynchronous programming. However, the async/await model in Rust also comes with its share of pitfalls and footguns. We illustrate some of them in this chapter:
44

5-
---
6-
75
- [Blocking the Executor](pitfalls/blocking-executor.md)
86
- [Pin](pitfalls/pin.md)
97
- [Async Traits](pitfall/async-traits.md)

src/async/pitfalls/async-traits.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The crate [async_trait](https://docs.rs/async-trait/latest/async_trait/) provide
66

77
```rust,editable,compile_fail
88
use async_trait::async_trait;
9+
use std::time::Instant;
910
use tokio::time::{sleep, Duration};
1011
1112
#[async_trait]
@@ -26,8 +27,11 @@ impl Sleeper for FixedSleeper {
2627
2728
async fn run_all_sleepers_multiple_times(sleepers: Vec<Box<dyn Sleeper>>, n_times: usize) {
2829
for _ in 0..n_times {
30+
println!("running all sleepers..");
2931
for sleeper in &sleepers {
32+
let start = Instant::now();
3033
sleeper.sleep().await;
34+
println!("slept for {}ms", start.elapsed().as_millis());
3135
}
3236
}
3337
}
@@ -44,7 +48,11 @@ async fn main() {
4448

4549
<details>
4650

51+
* `async_trait` is easy to use, but note that it's using heap allocations to
52+
achieve this, which has performance implications.
53+
4754
* Try creating a new sleeper struct that will sleep for a random amount of time and adding it to the Vec.
48-
* Try making the `sleep` call mutable.
49-
* Try adding an associated type for the return value that would return how much time was actually slept.
50-
</details>
55+
56+
* Try making the `sleep` call take `&mut self`.
57+
58+
</details>

0 commit comments

Comments
 (0)