0Pricing
Learn Rust Coding · Lekcja

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 fn w Rust w rzeczywistości zwracają anonimowy typ implementujący cechę Future.
  • Wartość nie jest obliczana natychmiast — jest obliczana, gdy wykonawca wykonuje na obiekcie typu Future operację „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 Future zwraca Poll::Pending (jeszcze niezakończony) albo Poll::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 Future jest cechą reprezentującą obliczenie asynchroniczne, które ostatecznie zwróci wartość.
  • Funkcje async fn są 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::spawn służy do tworzenia nowego zadania i zwraca obiekt JoinHandle.
  • Na obiekcie JoinHandle wykonuje 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

  1. Wprowadzenie do Async/Await
  2. Tworzenie aplikacji asynchronicznych za pomocą Tokio
  3. Praca z futures i zadaniami
← Powrót do Learn Rust Coding