Encontrei um fenômeno ao aprender multithreading Rust tokio, não sei por quê。
Aqui está o código.
use std::{thread, time::Duration};
use chrono::Local;
fn now() -> String {
Local::now().format("%F %T").to_string()
}
async fn async_task(i: u64) {
thread::sleep(Duration::from_secs(i));
println!("{}.async task {}!", now(), i);
}
#[tokio::main]
async fn main() {
for i in 0..10 {
tokio::spawn(async_task(i));
}
println!("{}.main thread", now());
}
Executei o código e descobri que cada 10 tarefas assíncronas foram executadas. O resultado foi ao vivo abaixo
2023-09-05 22:08:05.async task 0!
2023-09-05 22:08:05.main thread
2023-09-05 22:08:06.async task 1!
2023-09-05 22:08:07.async task 2!
2023-09-05 22:08:08.async task 3!
2023-09-05 22:08:09.async task 4!
2023-09-05 22:08:10.async task 5!
2023-09-05 22:08:11.async task 6!
2023-09-05 22:08:12.async task 7!
2023-09-05 22:08:13.async task 8!
2023-09-05 22:08:14.async task 9!
Quando comento o println
código no main
thread, apenas algumas tarefas serão executadas.
#[tokio::main]
async fn main() {
for i in 0..10 {
tokio::spawn(async_task(i));
}
// println!("{}.main thread", now());
}
O resultado foi ao vivo abaixo
2023-09-05 22:10:51.async task 0!
2023-09-05 22:10:52.async task 1!
Já tentei isso muitas vezes e sempre ocorre essa diferença. Em cada tentativa, o código que não comenta println
executa todas as tarefas assíncronas e o outro não.
Eu realmente não entendo por que um println
poderia fazer uma diferença tão grande. Eu apreciaria se alguém pudesse ajudar.
A verdadeira razão pela qual seu código se comporta de maneira estranha é porque você usa
std::thread::sleep
em vez detokio::time::sleep
.É importante ao trabalhar com funções assíncronas que você nunca bloqueia. Os reatores assíncronos não são preemptivos , o que significa que só podem agendar entre tarefas em
.await
alguns pontos. Isso significa que, se vocêstd::thread::sleep
bloquear o programa inteiro. É também por isso que no seu programa de trabalho a saída nem sempre é impressa na ordem correta, embora o tempo deva ser de um segundo inteiro entre cada impressão.Se você substituir
std::thread::sleep
portokio::time::sleep
, obterá um comportamento consistente (embora provavelmente não seja o comportamento desejado):Por quê então? Porque quando
main
termina, o Tokio cancela todas as tarefas restantes. Mas, novamente, o tokio só pode agendar em.await
pontos, então no seu exemplo de 'trabalho', o tokio ainda tenta cancelar as tarefas, mas não tem sucesso porque elas nunca atingem um.await
ponto. Então ‘parece’ que Tóquio está esperando por eles, mas isso não é verdade.A maneira mais simples de resolver isso é não
main
encerrar a função. (na verdade, esta é a única maneira que me vem à mente)Existem várias maneiras de conseguir isso. No seu caso, me parece que sua intenção é aguardar o término de todas as subtarefas, para então encerrar o programa. Isso pode ser feito aguardando os identificadores de junção:
Dito isto, aguardá-los um por um é um pouco tedioso e também evita a propagação imediata de erros. Em vez disso, você poderia usar
futures::future::try_join_all
: