Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Rust e runtime per le attività asincrone

Rust: presentiamo il concetto di runtime per attività asincrone e facciamo la conoscenza di uno dei più usati in Rust: Tokio
Rust: presentiamo il concetto di runtime per attività asincrone e facciamo la conoscenza di uno dei più usati in Rust: Tokio
Link copiato negli appunti

Abbiamo già visto il meccanismo dei thread in Rust, il fondamento principale per gestire le attività asincrone su più corsie parallele. A questo punto, entrando nel vivo delle soluzioni che richiedono performance maggiori e soprattutto prima di parlare delle connessioni in Rete, è fondamentale fare la conoscenza di un contesto che si rivela basilare per un'organizzazione più strutturata e meno manuale quando le attività asincrone si intensificano.

Per questo motivo, presentiamo qui il concetto di runtime per attività asincrone e facciamo la conoscenza di uno dei più usati in Rust, Tokio.

Tokio: a cosa serve e perché è così usato in Rust

La grande fama ed il vasto utilizzo del runtime Tokio sono dovuti alla sua capacità di risolvere un problema che potrebbe essere molto intricato da trattare manualmente - quello di attività asincrone multiple - in modo molto lineare. Questo framework mette a disposizione dei "blocchi" che possono essere impiegati implementando in maniera molto elegante e semplice da usare i medesimi concetti che abbiamo visto con i thread.

Tokio presenta una struttura modulare che permette di integrare la libreria per intero o focalizzandoci solo su ciò di cui abbiamo bisogno. Con cargo la gestione delle dipendenze e della libreria stessa sarà molto semplice. Infatti, una volta che il progetto sarà stato approntato con il comando cargo init NOME_PROGETTO si potrà integrare Tokio modificando direttamente il file delle dipendenze Cargo.toml aggiungendo:

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

o digitando il comando:

cargo add tokio -F full

(qui abbiamo parlato del caso di un'integrazione full di tutta la libreria Tokio).

Sviluppo con attività asincrone

Un runtime asincrono offre delle parole chiave che attivano direttamente dei meccanismi e ciò renderà noi più produttivi ed il codice più leggibile. Nell'esempio che segue vedremo infatti alcuni aspetti che ritroveremo anche nelle prossime lezioni:

  • per prima cosa, assegneremo l'attributo #[tokio::main] alla funzione main che, come sappiamo, è l'entrypoint dell'esecuzione del programma;
  • await è una parola chiave che serve a sospendere l'esecuzione finché il risultato di un'elaborazione non sarà reso disponibile. Tecnicamente, tale elemento è di tipo Future il cui nome è già di per sé indizio che il risultato non sarà restituito immediatamente in maniera sincrona ma giungerà non appena pronto. Come vedremo, questa parola chiave accompagna richieste in Rete e asincrone in genere un po' in tutti i framework;
  • async altra parola chiave usata in associazione con await per marcare i blocchi di codice in cui quest'ultima potrà essere usata.

Vediamo ora il tutto al lavoro in un esempio pratico.

Un esempio di utilizzo in Rust

Nell'esempio dedicato a Rust che segue, partiamo da un progetto cargo in cui abbiamo importato Tokio per attivare una funzione asincrona che genererà un'attesa finta (per simulare un'elaborazione significativa) e restituirà un risultato, un Future di tipo Result<String, String>. L'elaborazione è del tutto fittizia in quanto il risultato sarà comunque un messaggio fisso.

use std::{thread, time};
async fn attivita_asincrona() -> Result<String, String>{
   let tre_secondi = time::Duration::from_secs(3);
   thread::sleep(tre_secondi);
   println!("THREAD {:?}: Elaborazione in corso", thread::current().id());
   thread::sleep(tre_secondi);
   println!("THREAD {:?}: Elaborazione finita", thread::current().id());
   Ok("Risultato finale".to_string())
}
#[tokio::main]
async fn main() {
   println!("THREAD {:?}: Inizia il programma", thread::current().id());
   let risultato = tokio::spawn(attivita_asincrona());
   println!("THREAD {:?}: Avviata attività asincrona", thread::current().id());
   let valore_estratto = risultato.await.unwrap();
   println!("Ecco il risultato dell'attività asincrona {:?}", valore_estratto.unwrap());
}

L'attività viene attivata su thread secondario tramite il meccanismo dello spawning, già visto, di cui si occuperà totalmente Tokio (vedere nel main tokio::spawn(attivita_asincrona())). Useremo anche la funzione thread::current().id() per ottenere l'identificatore univoco del thread e verificare che stiamo effettivamente lavorando su thread diversi (uno per il main e l'altro per la funzione asincrona).

Come si vede, il tutto funziona e i meccanismi risultano parallelizzati. Ecco l'output:

THREAD ThreadId(1): Inizia il programma
THREAD ThreadId(1): Avviata attività asincrona
THREAD ThreadId(2): Elaborazione in corso
THREAD ThreadId(2): Elaborazione finita
Ecco il risultato dell'attività asincrona "Risultato finale"

All'inizio di ogni riga riportiamo la trascrizione dell'oggetto ThreadId che ci dice se stiamo operando sul principale (id 1) o sul secondario (codice 2).

Ti consigliamo anche