Pin
Async blocks and functions return types implementing the Future
trait. The type returned is the result of a compiler transformation which turns local variables into data stored inside the future.
Some of those variables can hold pointers to other local variables. Because of that, the future should never be moved to a different memory location, as it would invalidate those pointers.
To prevent moving the future type in memory, it can only be polled through a pinned pointer. Pin
is a wrapper around a reference that disallows all operations that would move the instance it points to into a different memory location.
use tokio::sync::{mpsc, oneshot}; use tokio::task::spawn; use tokio::time::{sleep, Duration}; // Um item de trabalho. Neste caso, apenas dormir pelo tempo dado e responder com uma mensagem no canal `respond_on`. #[derive(Debug)] struct Work { input: u32, respond_on: oneshot::Sender<u32>, } // Um trabalhador que escuta o trabalho em uma fila e o executa. async fn worker(mut work_queue: mpsc::Receiver<Work>) { let mut iterations = 0; loop { tokio::select! { Some(work) = work_queue.recv() => { sleep(Duration::from_millis(10)).await; // Fingir trabalhar. work.respond_on .send(work.input * 1000) .expect("falha ao enviar resposta"); iterations += 1; } // TODO: relatar o nĂșmero de iteraçÔes a cada 100ms } } } // Um solicitante que solicita trabalho e aguarda sua conclusĂŁo. async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 { let (tx, rx) = oneshot::channel(); work_queue .send(Work { input, respond_on: tx }) .await .expect("falha ao enviar na fila de trabalho"); rx.await.expect("falha ao esperar pela resposta") } #[tokio::main] async fn main() { let (tx, rx) = mpsc::channel(10); spawn(worker(rx)); for i in 0..100 { let resp = do_work(&tx, i).await; println!("resultado do trabalho para a iteração {i}: {resp}"); } }
-
VocĂȘ pode reconhecer isso como um exemplo do padrĂŁo actor. Os actors tipicamente chamam
select!
em um loop. -
Isso serve como uma sĂntese de algumas das liçÔes anteriores, entĂŁo leve seu tempo com isso.
-
Adicione ingenuamente um
_ = sleep(Duration::from_millis(100)) => { println!(..) }
aoselect!
. Isso nunca serĂĄ executado. Por quĂȘ? -
Em vez disso, adicione um
timeout_fut
contendo essa future fora doloop
:#![allow(unused)] fn main() { let mut timeout_fut = sleep(Duration::from_millis(100)); loop { select! { .., _ = timeout_fut => { println!(..); }, } } }
-
Isso ainda nĂŁo funciona. Siga os erros do compilador, adicionando
&mut
aotimeout_fut
noselect!
para contornar a movimentação, e então usandoBox::pin
.#![allow(unused)] fn main() { let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100))); loop { select! { .., _ = &mut timeout_fut => { println!(..); }, } } }
-
Isso compila, mas uma vez que o timeout expira, ele Ă©
Poll::Ready
em cada iteração (uma fused future ajudaria com isso). Atualize para redefinirtimeout_fut
toda vez que expirar.
-
-
O Box aloca na pilha. Em alguns casos,
std::pin::pin!
(apenas recentemente estabilizado, com cĂłdigo mais antigo frequentemente usandotokio::pin!
) tambĂ©m Ă© uma opção, mas Ă© difĂcil de usar para uma future que Ă© reatribuĂda. -
Outra alternativa Ă© nĂŁo usar
pin
de forma alguma, mas iniciar outra tarefa que enviarĂĄ para um canaloneshot
a cada 100ms. -
Data that contains pointers to itself is called self-referential. Normally, the Rust borrow checker would prevent self-referential data from being moved, as the references cannot outlive the data they point to. However, the code transformation for async blocks and functions is not verified by the borrow checker.
-
Pin
is a wrapper around a reference. An object cannot be moved from its place using a pinned pointer. However, it can still be moved through an unpinned pointer. -
The
poll
method of theFuture
trait usesPin<&mut Self>
instead of&mut Self
to refer to the instance. Thatâs why it can only be called on a pinned pointer.