cat swift/async-let-parallelismo-locale.md
async let per parallelismo locale
Quando due operazioni asincrone sono indipendenti, async let permette di avviarle insieme mantenendo struttura e leggibilità.
Molto codice asincrono è sequenziale per abitudine, non per necessità.
let prodotto = try await api.prodotto(id: id)
let recensioni = try await api.recensioni(id: id)
let suggeriti = try await api.prodottiSuggeriti(id: id)
Se queste tre operazioni sono indipendenti, stai aspettando inutilmente.
async let permette di avviare lavori asincroni in parallelo, dentro uno scope strutturato.
Forma base
async let prodotto = api.prodotto(id: id)
async let recensioni = api.recensioni(id: id)
async let suggeriti = api.prodottiSuggeriti(id: id)
let dettaglio = try await DettaglioProdotto(
prodotto: prodotto,
recensioni: recensioni,
suggeriti: suggeriti
)
Le operazioni partono quando dichiari gli async let. I risultati vengono attesi quando li usi con await.
Con throws
Se le funzioni possono fallire:
async let prodotto = api.prodotto(id: id)
async let recensioni = api.recensioni(id: id)
return try await DettaglioProdotto(
prodotto: prodotto,
recensioni: recensioni
)
Il try await raccoglie eventuali errori.
Scope strutturato
Gli async let sono figli del task corrente. Non scappano dallo scope.
Questo significa:
- se la funzione viene cancellata, i figli vengono cancellati;
- se esci dallo scope, Swift deve gestire i task figli;
- il lifetime resta comprensibile.
Questo è molto diverso da creare task non strutturati.
Quando usare async let
Usalo quando:
- hai poche operazioni;
- il numero di task è noto a compile-time o comunque fisso;
- le operazioni sono indipendenti;
- vuoi risultati tipizzati e leggibili;
- vuoi mantenere codice locale.
Esempio:
func dashboard() async throws -> Dashboard {
async let profilo = api.profilo()
async let ordini = api.ordiniRecenti()
async let coupon = api.couponDisponibili()
return try await Dashboard(
profilo: profilo,
ordini: ordini,
coupon: coupon
)
}
Quando non usarlo
Non usarlo quando:
- il numero di task dipende da una collection;
- devi aggiungere task in un loop dinamico;
- vuoi limitare parallelismo;
- devi raccogliere risultati man mano.
In quei casi usa TaskGroup.
Dipendenze tra operazioni
Non parallelizzare ciò che dipende.
let utente = try await api.utente()
async let ordini = api.ordini(utenteID: utente.id)
async let coupon = api.coupon(utenteID: utente.id)
Qui prima serve l’utente; poi ordini e coupon possono partire insieme.
Errore comune: await troppo presto
Questo non parallelizza davvero:
async let prodotto = api.prodotto(id: id)
let prodottoValue = try await prodotto
async let recensioni = api.recensioni(id: id)
let recensioniValue = try await recensioni
Hai atteso il primo prima di avviare il secondo.
Avvia prima, attendi dopo:
async let prodotto = api.prodotto(id: id)
async let recensioni = api.recensioni(id: id)
let result = try await (prodotto, recensioni)
Checklist
Prima di usare async let, chiediti:
- le operazioni sono indipendenti?
- il numero di operazioni è piccolo e noto?
- posso avviarle prima e attenderle dopo?
- cosa succede se una fallisce?
- la cancellazione deve propagarsi?
Esercizio
Scrivi:
func caricaHome() async throws -> HomeModel
che carica in parallelo:
- profilo utente;
- ordini recenti;
- coupon disponibili.
Poi modifica l’esercizio: prima carica l’utente, poi usa il suo id per caricare ordini e coupon in parallelo.