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 1 commit
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
17 changes: 17 additions & 0 deletions src/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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 scales to higher concurrency than threads
because the per-task overhead is typically very low and operating systems
provide means of efficiently selecting tasks that can make progress.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: It sounds like there is inherent OS support for async/await. Perhaps reword to something like "the async/await implementation relies on OS primitive to efficiently select the next tasks to run".

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I addressed this, but I'm happy for more editing :)


## 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 an executor 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>
34 changes: 34 additions & 0 deletions src/async/async-blocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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