From 307ea61de61b7404ff93a1d3b1725181f47df5ed Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 9 Mar 2023 22:50:22 +0000 Subject: [PATCH 1/2] beginning of an Async section --- src/SUMMARY.md | 15 +++++++++++ src/async.md | 17 ++++++++++++ src/async/async-await.md | 52 ++++++++++++++++++++++++++++++++++++ src/async/async-blocks.md | 34 +++++++++++++++++++++++ src/exercises/day-4/async.md | 3 +++ 5 files changed, 121 insertions(+) create mode 100644 src/async.md create mode 100644 src/async/async-await.md create mode 100644 src/async/async-blocks.md create mode 100644 src/exercises/day-4/async.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index bf5800a8e8f0..0f9a1466d8fc 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/async.md b/src/async.md new file mode 100644 index 000000000000..d4f7d3ad536e --- /dev/null +++ b/src/async.md @@ -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. + +## 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. diff --git a/src/async/async-await.md b/src/async/async-await.md new file mode 100644 index 000000000000..4414c9b367cd --- /dev/null +++ b/src/async/async-await.md @@ -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; + +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; + } +} +``` + +
+ +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. + +
diff --git a/src/async/async-blocks.md b/src/async/async-blocks.md new file mode 100644 index 000000000000..06038151e8f9 --- /dev/null +++ b/src/async/async-blocks.md @@ -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}"); + } +} + +
+ +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. + +
diff --git a/src/exercises/day-4/async.md b/src/exercises/day-4/async.md new file mode 100644 index 000000000000..14047188461c --- /dev/null +++ b/src/exercises/day-4/async.md @@ -0,0 +1,3 @@ +# Exercises + +TBD From 7515eb2e6e610edd2c759a1f20ef10c4655dd634 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 10 Mar 2023 16:34:13 +0000 Subject: [PATCH 2/2] address review comments --- src/async.md | 16 ++++++++++++---- src/async/async-blocks.md | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/async.md b/src/async.md index d4f7d3ad536e..cd2487dd522a 100644 --- a/src/async.md +++ b/src/async.md @@ -2,15 +2,23 @@ "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. +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 an executor in Rust. + 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 diff --git a/src/async/async-blocks.md b/src/async/async-blocks.md index 06038151e8f9..57115c1fc88d 100644 --- a/src/async/async-blocks.md +++ b/src/async/async-blocks.md @@ -24,6 +24,7 @@ async fn main() { println!("task joined with result: {greeting}"); } } +```