I'm new to asynchronous programming, therefore struggling with how different approach varies in behavior.
Consider the example given by tokio at github repo, chat.rs:
// snip
loop {
tokio::select! {
// A message was received from a peer. Send it to the current user.
Some(msg) = peer.rx.recv() => {
// do something
}
result = peer.lines.next() => match result {
// A message was received from the current user, we should
// broadcast this message to the other users.
Some(Ok(msg)) => {
// do something
}
Some(Err(e)) => {
// handle error
}
// The stream has been exhausted.
None => break,
},
}
}
// do something
What is the benefit of using loop select! over two tokio::spawn, like below:
let handle_1 = tokio::spawn(async move {
while let Some(msg) = peer.rx.recv() {
// do something
}
});
let handle_2 = tokio::spawn (async move {
loop {
let result = peer.lines.next();
match result {
Some(Ok(msg)) => {
// do something
},
Some(Err(e)) => {
// handle error
},
None => break,
};
}
});
handle_1.await;
handle_2.await;
// do something
In general, select!
is more efficient because it doesn't need to spawn new tasks, which is very cheap but still more expensive than just polling futures. However, there are few caveats:
If there are frequent messages, which makes the task more CPU-bound, it is recommend to spawn a new task, because select!
run all futures on the same thread while spawn()
may use a different thread in a multi-threaded runtime.
The futures in select!
should be cancellation-safe, meaning it is safe to drop them incompleted and this does not cause loss of any data. If this is not the case, there may be bugs. It makes it harder to program with select!
than with tasks. See this post for example.