Pin
بلوکها و توابع Async انواعی را برمیگردانند که ویژگی Future
را پیادهسازی میکنند. نوع برگشتی نتیجه تبدیل کامپایلر است که متغیرهای محلی را به داده های ذخیره شده در future تبدیل می کند.
برخی از این متغیرها میتوانند اشارهگرهایی را برای سایر متغیرهای محلی نگه دارند. به همین دلیل، future هرگز نباید به مکان حافظه دیگری منتقل شود، زیرا این pointerها را باطل میکند.
برای جلوگیری از جابجایی تایپ future در حافظه، فقط از طریق یک pointer پین شده می توان آن را بررسی کرد. Pin
یک wrapper در اطراف یک reference است که تمام عملیاتی را که میتواند نمونهای را که به آن اشاره میکند به یک مکان حافظه متفاوت منتقل کند را ممنوع میکند.
use tokio::sync::{mpsc, oneshot}; use tokio::task::spawn; use tokio::time::{sleep, Duration}; // A work item. In this case, just sleep for the given time and respond // with a message on the `respond_on` channel. #[derive(Debug)] struct Work { input: u32, respond_on: oneshot::Sender<u32>, } // A worker which listens for work on a queue and performs it. 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; // Pretend to work. work.respond_on .send(work.input * 1000) .expect("failed to send response"); iterations += 1; } // TODO: report number of iterations every 100ms } } } // A requester which requests work and waits for it to complete. 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("failed to send on work queue"); rx.await.expect("failed waiting for response") } #[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!("نتیجه کار برای تکرار {i}: {resp}"); } }
-
شما ممکن است این را به عنوان نمونه ای از الگوی بازیگر (actor pattern) تشخیص دهید. بازیگران معمولاً
select!
را در یک حلقه صدا میزنند. -
این به عنوان یک جمعبندی از چند درس قبلی عمل می کند، بنابراین وقت خود را صرف آن کنید.
-
بهسادگی یک
_ = sleep(Duration::from_millis(100)) => { println!(..) }
را بهselect!
اضافه کنید. این مورد هرگز اجرا نمی شود. چرا؟ -
درعوض، یک
timeout_fut
حاوی آن future خارج ازloop
اضافه کنید:#![allow(unused)] fn main() { let timeout_fut = sleep(Duration::from_millis(100)); loop { select! { .., _ = timeout_fut => { println!(..); }, } } }
-
This still doesn't work. Follow the compiler errors, adding
&mut
to thetimeout_fut
in theselect!
to work around the move, then usingBox::pin
:#![allow(unused)] fn main() { let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100))); loop { select! { .., _ = &mut timeout_fut => { println!(..); }, } } }
-
این مورد کامپایل میشود، اما پس از انقضای timeout و در هر تکرار برابر با
Poll::Ready
است (future ترکیبی به این مسئله کمک میکند). بهروزرسانی برای بازنشانیtimeout_fut
هر بار که منقضی میشود:#![allow(unused)] fn main() { let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100))); loop { select! { _ = &mut timeout_fut => { println!(..); timeout_fut = Box::pin(sleep(Duration::from_millis(100))); }, } } }
-
-
Box allocates on the heap. In some cases,
std::pin::pin!
(only recently stabilized, with older code often usingtokio::pin!
) is also an option, but that is difficult to use for a future that is reassigned. -
جایگزین دیگر این است که به هیچ وجه از
pin
استفاده نکنید، بلکه task دیگری ایجاد کنید که هر 100 میلیثانیه به یک کانالoneshot
ارسال میشود. -
دادههایی که حاوی اشارهگرهایی به خود هستند، خود ارجاعی ( self-referential) نامیده می شوند. به طور معمول، Rust borrow checker از جابجایی دادههای خودارجاعی جلوگیری میکند، زیرا منابع نمیتوانند بیشتر از دادههایی که به آنها اشاره میکنند زنده بمانند. بااینحال، تبدیل کد برای بلوکها و توابع async توسط borrow checker تأیید نمیشود.
-
Pin
یک wrapper در اطراف یک reference است. یک object را نمیتوان با استفاده از یک pointer پین شده از جای خود حرکت داد. با این حال، هنوز هم میتوان آن را از طریق یک pointer بدون پین جابجا کرد. -
متد
poll
از ویژگیFuture
ازPin<&mut Self>
به جای&mut Self
برای اشاره به نمونه (instance) استفاده میکند. به همین دلیل است که فقط میتوان آن را روی یک اشارهگر پین شده فراخوانی کرد.