Praca z futures i zadaniami
Dowiedzą się Państwo, czym jest trait `Future` oraz jak zadania są planowane i zarządzane w środowisku runtime’u asynchronicznego.
Praca z futures i zadaniami to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 3 z 3. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Learn Rust Coding, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Learn Rust Coding zawiera 3 lekcji w sumie.
Czym jest Future w Rust?
W asynchronicznym Rust obiekt typu Future jest cechą reprezentującą obliczenie asynchroniczne, które może zakończyć się w pewnym momencie. Można myśleć o nim jak o obietnicy wartości, która nie jest jeszcze gotowa.
- Jest podstawowym elementem asynchronicznego Rusta.
- Funkcje
async fnw Rust w rzeczywistości zwracają anonimowy typ implementujący cechęFuture. - Wartość nie jest obliczana natychmiast — jest obliczana, gdy wykonawca wykonuje na obiekcie typu
Futureoperację „poll”.
Jak obiekty Future robią postępy
Obiekt typu Future nie wykonuje się samodzielnie. Wykonawca (taki jak środowisko uruchomieniowe Tokio) wielokrotnie wykonuje na nim operację „poll”, aby sprawdzić, czy zrobił postęp lub zakończył działanie.
- Podczas odpytywania obiekt typu
FuturezwracaPoll::Pending(jeszcze niezakończony) alboPoll::Ready(T)(zakończony, oto wynik). - Jeśli zwrócona zostanie wartość
Pending, wykonawca wie, że powinien ponownie odpytać obiekt później, gdy wydarzy się coś istotnego (na przykład zakończy się operacja wejścia-wyjścia). - Ten mechanizm odpytywania pozwala wielu operacjom asynchronicznym działać współbieżnie w jednym wątku.
Kod async jest obiektem typu Future
Po napisaniu funkcji async fn Rust przekształca ją w automat stanów implementujący cechę Future. Właściwe obliczenia rozpoczynają się dopiero po odpytywaniu zwróconego obiektu typu Future.
Przyjrzyjmy się prostej funkcji async. Nie zostanie ona wykonana, dopóki wykonawca nie zaczeka na jej wynik.
async fn say_hello() -> String {
"Hello from a Future!".to_string()
}
#[tokio::main]
async fn main() {
let future = say_hello(); // This doesn't run the function yet!
println!("Future created, but not awaited.");
// To run it, an executor needs to poll it, often via .await
// let result = future.await;
// println!("{}", result);
}Obiekty Future stają się zadaniami
O ile obiekt typu Future jest definicją obliczenia asynchronicznego, o tyle zadanie jest aktywną instancją tego obiektu, doprowadzaną przez wykonawcę do zakończenia.
- Gdy polecą Państwo wykonawcy (takiemu jak Tokio) uruchomić obiekt typu
Future, opakuje go on w zadanie. - Następnie wykonawca zarządza tym zadaniem, odpytując je za każdym razem, gdy może ono kontynuować działanie.
- Zadania są jednostkami pracy planowanymi i wykonywanymi współbieżnie przez środowisko uruchomieniowe async.
Tworzenie zadań dla współbieżności
Aby wykonywać obiekt typu Future współbieżnie z innym kodem, należy „utworzyć” go w środowisku uruchomieniowym Tokio. Spowoduje to utworzenie nowego zadania, którym będzie zarządzać środowisko uruchomieniowe.
Funkcja tokio::spawn przyjmuje obiekt typu Future i zwraca obiekt JoinHandle, którego można użyć do oczekiwania na zakończenie zadania i pobrania jego wyniku.
async fn perform_task(id: u8) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
format!("Task {} finished!", id)
}
#[tokio::main]
async fn main() {
println!("Main started.");
let handle = tokio::spawn(async {
perform_task(1).await
});
println!("Task 1 spawned.");
// We will await 'handle' in the next scene to get the result.
// let result = handle.await.unwrap();
// println!("{}", result);
}Pobieranie wyników zadań
Obiekt JoinHandle zwrócony przez tokio::spawn sam jest obiektem typu Future. Można użyć .await na tym obiekcie, aby zaczekać na zakończenie utworzonego zadania i pobrać zwracaną przez nie wartość.
Jeśli utworzone zadanie zakończy się paniką, oczekiwanie na jego JoinHandle zwróci błąd.
async fn perform_task(id: u8) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
format!("Task {} finished!", id)
}
#[tokio::main]
async fn main() {
println!("Main started.");
let handle = tokio::spawn(async {
perform_task(1).await
});
println!("Task 1 spawned.");
let result = handle.await.unwrap(); // Await the handle to get the result
println!("{}", result);
println!("Main finished.");
}Wiele współbieżnych zadań
Możliwości zadań i tokio::spawn najlepiej widać podczas równoczesnego wykonywania wielu operacji. Środowisko uruchomieniowe Tokio efektywnie przełącza się między zadaniami, gdy stają się one gotowe.
Dzięki temu program może robić postępy w wielu obszarach jednocześnie bez blokowania, nawet w jednym wątku.
async fn long_task(id: u8) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(100 + id as u64 * 50)).await;
format!("Long task {} done!", id)
}
#[tokio::main]
async fn main() {
println!("Main started.");
let handle1 = tokio::spawn(long_task(1));
let handle2 = tokio::spawn(long_task(2));
println!("Both tasks spawned.");
let result1 = handle1.await.unwrap();
let result2 = handle2.await.unwrap();
println!("{}\n{}", result1, result2);
println!("Main finished.");
}Oczekiwanie na wszystkie zadania za pomocą join!
Gdy trzeba jednocześnie zaczekać na zakończenie kilku obiektów typu Future, przydatnym makrem jest tokio::join!. Czeka ono współbieżnie na wszystkie podane obiekty Future i zwraca ich wyniki jako krotkę.
Działa podobnie do osobnego oczekiwania na każdy uchwyt, ale często jest bardziej zwięzłe w przypadku stałej liczby obiektów typu Future.
async fn fetch_data(source: &str) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
format!("Data from {}", source)
}
#[tokio::main]
async fn main() {
println!("Starting data fetches...");
let (data_a, data_b) = tokio::join!(
fetch_data("Server A"),
fetch_data("Database B")
);
println!("Fetched: {}\nFetched: {}", data_a, data_b);
println!("All fetches complete.");
}Obsługa błędów zadań
Operacje asynchroniczne mogą kończyć się niepowodzeniem, podobnie jak operacje synchroniczne. Często obiekty typu Future zwracają typ Result wskazujący powodzenie lub niepowodzenie.
Podczas oczekiwania na obiekt JoinHandle należy pamiętać, że wynik jest opakowany w dodatkowy typ Result, ponieważ samo zadanie może zakończyć się paniką. Często można spotkać zapis handle.await? (wewnątrz funkcji async fn zwracającej Result) albo handle.await.unwrap() / .expect().
async fn might_fail(should_fail: bool) -> Result<String, &'static str> {
tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
if should_fail {
Err("Oops, something went wrong!")
} else {
Ok("Operation successful!".to_string())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let handle_ok = tokio::spawn(might_fail(false));
let handle_err = tokio::spawn(might_fail(true));
let result_ok = handle_ok.await??; // Await handle, then await inner Result
println!("{}", result_ok);
match handle_err.await? { // Await handle, then handle inner Result
Ok(val) => println!("{}", val),
Err(e) => eprintln!("Error: {}", e),
}
Ok(())
}Zrozumienie obiektów Future i zadań
Poznali Państwo obiekty Future jako obliczenia oraz zadania jako instancje wykonujące te obliczenia. Które stwierdzenie dotyczące tokio::spawn i JoinHandle jest prawdziwe?
Podsumowanie: obiekty Future i zadania
Świetna praca! Poznali już Państwo podstawowe pojęcia dotyczące obiektów typu Future i zadań w asynchronicznym ekosystemie Rusta:
- Obiekt typu
Futurejest cechą reprezentującą obliczenie asynchroniczne, które ostatecznie zwróci wartość. - Funkcje
async fnsą kompilowane do typów implementujących cechęFuture. - Zadanie jest instancją obiektu typu
Future, aktywnie zarządzaną i odpytywaną przez wykonawcę (takiego jak Tokio). tokio::spawnsłuży do tworzenia nowego zadania i zwraca obiektJoinHandle.- Na obiekcie
JoinHandlewykonuje się.await, aby uzyskać wynik utworzonego zadania.
Te podstawowe elementy są niezbędne do pisania wydajnych, nieblokujących aplikacji w Rust!
Często zadawane pytania
Czy lekcja „Praca z futures i zadaniami” jest bezpłatna?
Tak — pełny tekst „Praca z futures i zadaniami” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Learn Rust Coding, przejdź na CoddyKit PRO. Kurs Learn Rust Coding zawiera 3 lekcji w sumie.
Co nauczysz się w „Praca z futures i zadaniami”?
Dowiedzą się Państwo, czym jest trait `Future` oraz jak zadania są planowane i zarządzane w środowisku runtime’u asynchronicznego. Ćwiczysz Learn Rust Coding z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Learn Rust Coding?
Nie wymagamy żadnego doświadczenia. Learn Rust Coding w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 3.
Ile czasu zajmuje lekcja „Praca z futures i zadaniami”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Learn Rust Coding?
Tak. Każda lekcja Learn Rust Coding zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Wprowadzenie do Async/Await
- Tworzenie aplikacji asynchronicznych za pomocą Tokio
- Praca z futures i zadaniami