Add exercise async1#2382
Conversation
b2f334e to
7f50737
Compare
7f50737 to
46533ad
Compare
The goal here was to get the first bit of "muscle memory" for using the async and await keywords. The little story should make it more intuitive for users why asynchronous programming is needed in the first place. This exercise will be moved to the location corresponding to the book in a later commit, to keep the diff of this one clean.
46533ad to
abc8969
Compare
mo8it
left a comment
There was a problem hiding this comment.
I like
- The requirement of adding
asyncand.await - Spawning tasks, awaiting them and then checking that they are done
- The strory about splitting tasks among different workers
I don't like
- The usage of atomics
- The work done in each task. Printing locks under the hood so the tasks will run mostly sequentially even if the runtime was multi-threaded
- The story details about the boys and soccer. Some might find it a bit childish for tasks meant mainly for adults
What about letting the tasks do some calculation and return the result? Then all three results could be checked. This way, we don't need atomics or printing.
| // finish the chores earlier and have more time left to play soccer. | ||
| // | ||
| // Let's simulate this using asynchronous programming. Each boy is represented | ||
| // as an asynchronous task, which can be executed concurrently (they can be |
There was a problem hiding this comment.
Here, you say that they can be working at the same time. But in main, you create a single threaded runtime.
There was a problem hiding this comment.
"concurrently" is the more precise word. I think it's good for teaching to use a single-threaded runtime, to show that async can make a difference even without multi-threading. Though, as you mentioned, just printing to the console might not be best for that.
|
What about using Something like that does actual work and is a valid usage for async. |
|
The story could be something like teachers want to calculate the mean grade for three different classes. Instead of only one teacher doing all the work or doing it sequentially, they can do it async. |
- Remove confusing use of atomics. Use return values of async tasks instead, to ensure all tasks are awaited. - Remove use of `println!()`, which uses a global lock and cannot be executed in parallel.
|
Agree with most of these points. I redid the exercise to hopefully address them. I'm not sure yet about using a multi-threaded runtime and using file IO. Yes, it would be more realistic, but I'm concerned it might be more complicated and distract from the intended lesson here - building a little async/await muscle memory. |
There was a problem hiding this comment.
I like the overall direction, but I think that trying to keep the exercise simple results in misleading the users. By avoiding async work, they won't get the difference to multi-threading and will be confused because the solution is slower on a single thread.
| CHORES_DONE.fetch_add(1, Ordering::SeqCst); | ||
| assert_eq!(mean_score_a, 84); | ||
| assert_eq!(mean_score_b, 89); | ||
| assert_eq!(mean_score_c, 76); |
There was a problem hiding this comment.
The assertions can be moved to the body of block_on to avoid the indirection by the array.
| // Alice is an elementary school teacher who needs to calculate the mean test | ||
| // score for three classes she teaches. Instead of calculating them one after | ||
| // the other, she decides to ask her friends Bob and Catherine for help. Working | ||
| // together, they can finish the job much faster. |
There was a problem hiding this comment.
Using a single thread without actual async work means that they won't finish much faster, in fact, they will be slightly slower.
| static CHORES_DONE: AtomicU8 = AtomicU8::new(0); | ||
| // Let's simulate this using asynchronous programming. Each person is | ||
| // represented as an asynchronous task, which can be executed concurrently (i.e. | ||
| // they can be doing the calculations at the same time). |
There was a problem hiding this comment.
"at the same time" is misleading
|
Maybe somthing like this? fn main() {
// Async tasks need to be executed by a "runtime", which is not provided by
// Rust's standard library. Here, we use the mainstream runtime `tokio`.
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let alice = rt.spawn(calculate_mean_score("a.txt"));
let bob = rt.spawn(calculate_mean_score("b.txt"));
let catherine = rt.spawn(calculate_mean_score("c.txt"));
// Block the runtime on a task that awaits all three calculations.
rt.block_on(async {
// TODO: "await" all three tasks.
assert_eq!(alice, 84);
assert_eq!(bob, 89);
assert_eq!(catherine, 76);
});
}
// TODO: Fix the compiler errors by making the spawned function async.
fn calculate_mean_score(scores_file: &str) -> usize {
// Read the file asynchronously
let file = tokio::fs::read_to_string(scores_file).await.unwrap();
// Initialize the sum and the number of scores
let mut sum = 0;
let mut n = 0;
for line in file.lines() {
// Parse every line as a score
let score = line.parse::<usize>().unwrap();
sum += score;
n += 1;
}
sum / n
}It is not much more complicated. Remember that this exercise is one towards the end, so the users aren't complete beginners at this point. If we take this direction, we should add the IO tasks before. |
The goal here was to get the first bit of "muscle memory" for using the async and await keywords. The little story should make it more intuitive for users why asynchronous programming is needed in the first place.
This exercise can be moved to the location corresponding to the book in a later PR, to keep the diff of this one clean.