8.1 - מכונת המצבים של Future ושל async - פתרון
פתרון - התקדמות השהיה והשמדה¶
פתרון ראשון - מצב נשמר בין בדיקות¶
המשתנה שמונה בדיקות חייב להשתייך לעתיד עצמו. משתנה מקומי שנוצר מחדש בתוך poll יחזור לאפס בכל קריאה ולא יתאר התקדמות. המימוש הבא מרכז את שלושת התרגילים בתוכנית בדיקות אחת:
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
use std::task::{Context, Poll, Wake, Waker};
struct WakeCount(AtomicUsize);
impl Wake for WakeCount {
fn wake(self: Arc<Self>) { self.0.fetch_add(1, Ordering::Relaxed); }
}
struct WaitN { left: usize }
impl Future for WaitN {
type Output = u32;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> {
if self.left == 0 { return Poll::Ready(11); }
self.left -= 1;
cx.waker().wake_by_ref();
Poll::Pending
}
}
struct Probe(Arc<AtomicUsize>);
impl Drop for Probe {
fn drop(&mut self) { self.0.fetch_add(1, Ordering::Relaxed); }
}
async fn tracked(input: Probe, drops: Arc<AtomicUsize>) -> u32 {
let local = Probe(drops);
let result = WaitN { left: 2 }.await;
drop(local);
drop(input);
result * 2
}
fn main() {
let wake_count = Arc::new(WakeCount(AtomicUsize::new(0)));
let waker = Waker::from(wake_count.clone());
let mut cx = Context::from_waker(&waker);
let drops = Arc::new(AtomicUsize::new(0));
drop(tracked(Probe(drops.clone()), drops.clone()));
assert_eq!(drops.load(Ordering::Relaxed), 1);
drops.store(0, Ordering::Relaxed);
let mut f = Box::pin(tracked(Probe(drops.clone()), drops.clone()));
assert!(f.as_mut().poll(&mut cx).is_pending());
assert_eq!(drops.load(Ordering::Relaxed), 0);
drop(f);
assert_eq!(drops.load(Ordering::Relaxed), 2);
drops.store(0, Ordering::Relaxed);
wake_count.0.store(0, Ordering::Relaxed);
let mut f = Box::pin(tracked(Probe(drops.clone()), drops.clone()));
for expected in 1..=2 {
assert!(f.as_mut().poll(&mut cx).is_pending());
assert_eq!(wake_count.0.load(Ordering::Relaxed), expected);
}
assert_eq!(f.as_mut().poll(&mut cx), Poll::Ready(22));
assert_eq!(drops.load(Ordering::Relaxed), 2);
drop(f);
assert_eq!(drops.load(Ordering::Relaxed), 2);
let mut immediate = Box::pin(async { std::future::ready(11).await * 2 });
let before = wake_count.0.load(Ordering::Relaxed);
assert_eq!(immediate.as_mut().poll(&mut cx), Poll::Ready(22));
assert_eq!(wake_count.0.load(Ordering::Relaxed), before);
println!("all checks passed");
}
שמרו את הקוד בקובץ solution.rs והריצו:
הפלט הצפוי הוא הודעת הצלחה אחת. כשל בטענה עוצר את התוכנית ומציין איזו הנחה לא התקיימה.
פתרון שני - הפרמטר כבר בבעלות העתיד¶
בתרחיש הראשון האובייקט שמשמש כפרמטר נוצר לפני יצירת העתיד, והבעלות עליו עוברת אל העתיד. לכן השמדת העתיד מנקה אותו גם אם הגוף לא התחיל לרוץ. האובייקט המקומי עדיין לא נוצר, ולכן המונה מגיע רק לאחד.
בתרחיש השני הבדיקה הראשונה נכנסת לגוף ויוצרת גם את האובייקט המקומי. ההמתנה משאירה את שניהם חיים במצב השמור. השמדת העתיד מנקה את שני האובייקטים בלי לבצע את החישוב שאחרי ההמתנה. בתרחיש השלישי הגוף מגיע להשלמה ומבצע את הניקוי המפורש. השמדת המעטפת לאחר מכן אינה משמידה אותם שוב.
פתרון שלישי - נקודת המתנה שאינה משהה¶
העתיד המוכן מחזיר ערך כבר בבדיקה הראשונה שלו. לכן החישוב החיצוני ממשיך ומחזיר עשרים ושתיים באותה בדיקה, בלי לבקש יקיצה. אפשר להסיק מכך שאין כאן השהיה; אי אפשר להסיק שמנוע ביצוע כלשהו תמיד הוגן רק משום שהקוד כולל הרבה ביטויי await.
הבדיקות אינן בונות מנגנון תזמון. הן בודקות עתידים בעלי התנהגות ידועה באמצעות מספר מדויק של קריאות. תכנון מנוע כללי מחייב בנוסף טיפול ברישום יקיצה, משימות חדשות והשלמה, כפי שנלמד בהרצאות הבאות.