Skip to content

Introduce an alternative day-4-afternoon: async #492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,21 @@
- [With Java](android/interoperability/java.md)
- [Exercises](exercises/day-4/android.md)

# Day 4: Afternoon (Async)

----

- [Async](async.md)
- [async/await](async/async-await.md)
- [Async Blocks](async/async-blocks.md)
- Futures
- Executors
- Polling
- Pin
- Channels
- Select
- [Exercises](exercises/day-4/async.md)

# Final Words

- [Thanks!](thanks.md)
Expand Down
25 changes: 25 additions & 0 deletions src/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Async Rust

"Async" is a concurrency model where multiple tasks are executed concurrently by
executing each task until it would block, then switching to another task that is
ready to make progress. The model allows running a larger number of tasks on a
limited number of threads. This is because the per-task overhead is typically
very low and operating systems provide primitives for efficiently identifying
I/O that is able to proceed.

Rust's asynchronous operation is based around "futures", which represent work
that may be completed in the future. Futures are "polled" until they signal that
they are complete.

Futures are polled by an async runtime, and several different runtimes are
available.

## Comparisons

* Python has a similar model in its `asyncio`. However, its `Future` type is
callback-based, and not polled. Async Python programs require a "loop",
similar to a runtime in Rust.

* JavaScript's `Promise` is similar, but again callback-based. The language
runtime implements the event loop, so many of the details of Promise
resolution are hidden.
52 changes: 52 additions & 0 deletions src/async/async-await.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# `async`/`await`

At a high level, async Rust code looks very much like "normal" sequential code:

```rust,editable
use tokio::time;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to skip using tokio in this first example, and use futures and block_on?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

futures does not provide any time-based waking functionality. The only functionality in futures that would be non-trivial would be to send data over a channel, but I want to keep this example simple and parallel to the threads example.

Using futures for one of the later examples is a good idea, though.


async fn count_to(i: i32) {
for i in 1..10 {
println!("Count in task: {i}!");
time::sleep(time::Duration::from_millis(5)).await;
}
}

#[tokio::main]
async fn main() {
tokio::spawn(count_to(10));

for i in 1..5 {
println!("Main task: {i}");
time::sleep(time::Duration::from_millis(5)).await;
}
}
```

<details>

Key points:

* Tokio is one of several async runtimes available for Rust.

* The function is decorated with the "async" keyword to indicate that it is async. The
`tokio::main` macro invocation is a convenience to wrap the `main` function as a task.

* The `spawn` function creates a new, concurrent "task", just like spawning a thread.

* Whenever a task would block, we add an `.await` which returns control to the runtime until the
blocking operation is ready to proceed.

Further exploration:

* Why does `count_to` not (usually) get to 10? This is an example of async cancellation.
`tokio::spawn` returns a handle which can be awaited to wait until it finishes.

* Try `count_to(10).await` instead of spawning.

* Try importing `tokio::join` and using it to join multiple handles.

Note that the Rust playground does not allow network connections, so examples like making HTTP
requests are not possible.

</details>
35 changes: 35 additions & 0 deletions src/async/async-blocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Async Blocks

Similar to closures, a snippet of async code can be included inline in another
function with an async block:

```rust, editable
use tokio::{time, task};

#[tokio::main]
async fn main() {
let mut joinset = task::JoinSet::new();

for i in 1..5 {
joinset.spawn(async move {
println!("task {i} starting");
time::sleep(time::Duration::from_millis(i)).await;
println!("task {i} done");
format!("hello from task {i}")
});
}

while let Some(res) = joinset.join_next().await {
let greeting = res.unwrap();
println!("task joined with result: {greeting}");
}
}
```

<details>

An async block is similar to a closure, but does not take any arguments.

Its return value is a Future, which is described on the next slide.

</details>
3 changes: 3 additions & 0 deletions src/exercises/day-4/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Exercises

TBD