Увод у асинхроно програмирање у Русту

Uobičajeni modeli sinhronog programiranja često dovode do uskih grla u performansama. Razlog tome je što program čeka da se sporije operacije završe pre nego što pređe na sledeći zadatak. Ovo neretko rezultira neefikasnom upotrebom resursa i sporim odzivom za korisnike.

Asinhrono programiranje omogućava pisanje koda koji ne blokira izvršavanje i efikasno koristi sistemske resurse. Upotrebom asinhronog programiranja, moguće je kreirati aplikacije koje istovremeno obavljaju više zadataka. Asinhrono programiranje je posebno korisno za obradu mrežnih zahteva ili velikih količina podataka bez zaustavljanja toka izvršavanja.

Asinhrono programiranje u Rustu

Rustov model asinhronog programiranja omogućava pisanje efikasnog Rust koda koji se izvršava istovremeno, bez blokiranja toka izvršavanja. Asinhrono programiranje je od velike pomoći pri radu sa I/O operacijama, mrežnim zahtevima i zadacima koji podrazumevaju čekanje na spoljne resurse.

U Rust aplikacijama, asinhrono programiranje se može implementirati na različite načine, uključujući jezičke karakteristike, biblioteke i Tokio runtime.

Pored toga, Rustov model vlasništva i primitivi za konkurentnost, kao što su kanali i brave, omogućavaju bezbedno i efikasno istovremeno programiranje. Ove funkcionalnosti, u kombinaciji sa asinhronim programiranjem, omogućavaju izgradnju konkurentnih sistema koji se dobro skaliraju i koriste više CPU jezgara.

Rustovi koncepti asinhronog programiranja

Budućnost (Future) predstavlja temelj asinhronog programiranja u Rustu. Future predstavlja asinhrono izračunavanje koje još nije u potpunosti izvršeno.

Future su „lenje“ – izvršavaju se samo kada su „anketirane“ (polled). Kada se pozove metod `poll()`, proverava se da li je Future završena ili je potreban dodatni rad. Ako Future nije spremna, vraća `Poll::Pending`, što ukazuje da zadatak treba zakazati za kasniju izvršavanje. Ako je Future spremna, vraća `Poll::Ready` sa rezultujućom vrednošću.

Rustov standardni alatni lanac uključuje asinhroni I/O primitive, asinhronu verziju U/I datoteka, umrežavanje i tajmere. Ovi primitivi omogućavaju izvršavanje I/O operacija asinhrono. Na taj način se izbegava blokiranje izvršavanja programa dok se čeka da se I/O zadaci završe.

Sintaksa `async/await` omogućava pisanje asinhronog koda koji izgleda slično sinhronom kodu, čineći ga intuitivnijim i lakšim za održavanje.

Rustov pristup asinhronom programiranju stavlja naglasak na bezbednost i performanse. Pravila o vlasništvu i pozajmljivanju garantuju bezbednost memorije i sprečavaju uobičajene probleme konkurentnosti. Sintaksa `async/await` i koncept budućnosti (Future) pružaju intuitivan način za izražavanje asinhronih tokova posla. Za upravljanje zadacima i njihovo efikasno izvršavanje, mogu se koristiti runtime-ovi treće strane.

Kombinacijom ovih jezičkih karakteristika, biblioteka i runtime-ova, moguće je pisati kod sa visokim performansama. Pruža se moćan i ergonomski okvir za izgradnju asinhronih sistema, što Rust čini popularnim izborom za projekte koji zahtevaju efikasno upravljanje I/O zadacima i visok nivo konkurentnosti.

Rust verzija 1.39 i novije ne podržavaju asinhone operacije u standardnoj Rust biblioteci. Za korišćenje `async/await` sintakse za asinhone operacije u Rustu, biće potrebni sanduci treće strane. Mogu se koristiti paketi nezavisnih proizvođača kao što su Tokio ili `async-std` za rad sa `async/await` sintaksom.

Asinhrono programiranje sa Tokiom

Tokio je robustan asinhroni runtime za Rust, koji pruža funkcionalnost za izgradnju visokoučinkovitih i skalabilnih aplikacija. Omogućava iskorišćavanje moći asinhronog programiranja uz dodatne funkcije za skalabilnost.

U srcu Tokija je model asinhronog planiranja i izvršavanja zadataka. Tokio omogućava pisanje asinhronog koda pomoću sintakse `async/await`, što omogućava efikasno korišćenje sistemskih resursa i istovremeno izvršavanje zadataka. Tokio-ova petlja događaja efikasno upravlja zakazivanjem zadataka, obezbeđujući optimalno iskorišćenje CPU jezgara i minimalizaciju troškova promene konteksta.

Tokio kombinatori olakšavaju koordinaciju i sastavljanje zadataka. Tokio pruža moćne alate za koordinaciju i kompoziciju zadataka. Moguće je sačekati da se više zadataka završi pomoću `join`, odabrati prvi završen zadatak pomoću `select` i takmičiti se između više zadataka pomoću `race`.

Potrebno je dodati `tokio` sanduk u odeljak zavisnosti u fajlu `Cargo.toml`.

[dependencies]
tokio = { version = "1.9", features = ["full"] }

Evo kako se može koristiti sintaksa `async/await` u Rust programima sa Tokiom:

use tokio::time::sleep;
use std::time::Duration;

async fn hello_world() {
    println!("Hello, ");
    sleep(Duration::from_secs(1)).await;
    println!("World!");
}

#[tokio::main]
async fn main() {
    hello_world().await;
}

Funkcija `hello_world` je asinhrona, tako da može koristiti ključnu reč `await` da pauzira svoje izvršavanje dok se budućnost ne reši. Funkcija `hello_world` ispisuje „Hello,“ na konzolu. Poziv funkcije `Duration::from_secs(1)` suspenduje izvršavanje funkcije na jednu sekundu. Ključna reč `await` čeka da se budućnost spavanja završi. Na kraju, funkcija `hello_world` ispisuje „World!“ na konzolu.

Glavna funkcija je asinhrona funkcija sa atributom `#[tokio::main]`, koji označava glavnu funkciju kao ulaznu tačku za Tokio runtime. `hello_world().await` asinhrono izvršava funkciju `hello_world`.

Odlaganje zadataka sa Tokiom

Uobičajen zadatak u asinhronom programiranju je korišćenje odlaganja ili zakazivanje zadataka za izvršavanje u određenom vremenskom periodu. Tokio runtime obezbeđuje mehanizam za korišćenje asinhronih tajmera i odlaganja kroz modul `tokio::time`.

Evo kako se može odložiti operacija sa Tokio runtime-om:

use std::time::Duration;
use tokio::time::sleep;

async fn delayed_operation() {
    println!("Performing delayed operation...");
    sleep(Duration::from_secs(2)).await;
    println!("Delayed operation completed.");
}

#[tokio::main]
async fn main() {
    println!("Starting...");
    delayed_operation().await;
    println!("Finished.");
}

Funkcija `delayed_operation` uvodi kašnjenje od dve sekunde pomoću metode `sleep`. Funkcija `delayed_operation` je asinhrona, tako da može koristiti `await` za pauziranje izvršavanja dok se kašnjenje ne završi.

Rukovanje greškama u asinhronim programima

Rukovanje greškama u asinhronom Rust kodu uključuje korišćenje tipa `Result` i rukovanje Rust greškama pomoću `?` operatora.

use tokio::fs::File;
use tokio::io;
use tokio::io::{AsyncReadExt};

async fn read_file_contents() -> io::Result<String> {
    let mut file = File::open("file.txt").await?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;
    Ok(contents)
}

async fn process_file() -> io::Result<()> {
    let contents = read_file_contents().await?;

    Ok(())
}

#[tokio::main]
async fn main() {
    match process_file().await {
        Ok(()) => println!("File processed successfully."),
        Err(err) => eprintln!("Error processing file: {}", err),
    }
}

Funkcija `read_file_contents` vraća `io::Result` koji predstavlja mogućnost I/O greške. Korišćenjem `?` operatora nakon svake asinhron operacije, Tokio runtime će propagirati greške u stek poziva.

Glavna funkcija obrađuje rezultat pomoću `match` izraza, koji ispisuje tekst u zavisnosti od ishoda operacije.

Reqwest koristi asinhrono programiranje za HTTP operacije

Mnogi popularni sanduci, uključujući Reqwest, koriste Tokio za obezbeđivanje asinhronih HTTP operacija.

Korišćenjem Tokija sa Reqwest-om, moguće je uputiti više HTTP zahteva bez blokiranja drugih zadataka. Tokio pomaže u upravljanju hiljadama istovremenih veza i efikasnom korišćenju resursa.