cat swift/async-await-sospendere-senza-bloccare.md
Async e await: sospendere senza bloccare
La base della concurrency moderna in Swift: scrivere codice asincrono leggibile, sapendo cosa significa sospendere una funzione.
async e await permettono di scrivere codice asincrono con una forma lineare. La cosa importante è capire che await non significa “blocca il thread”. Significa: questa funzione può sospendersi mentre aspetta un risultato.
Questo cambia il modo in cui scrivi networking, lettura file, operazioni lente, chiamate a database, cache remote e UI.
Il problema delle callback
Prima di async/await, molto codice asincrono era scritto con callback:
func caricaProdotto(id: String, completion: @escaping (Result<Prodotto, Error>) -> Void) {
api.fetchProduct(id: id) { result in
completion(result)
}
}
Con molte operazioni concatenate, le callback diventano annidate e difficili da leggere.
Funzione async
Una funzione asincrona dichiara async:
func caricaProdotto(id: String) async throws -> Prodotto {
try await api.fetchProduct(id: id)
}
La firma dice:
- può sospendersi (
async); - può fallire (
throws); - restituisce un
Prodotto.
La chiamata richiede await:
let prodotto = try await caricaProdotto(id: "p-1")
await come punto di sospensione
Ogni await è un punto in cui la funzione può sospendersi.
let prodotto = try await repository.prodotto(id: id)
let recensioni = try await repository.recensioni(per: prodotto.id)
Queste due operazioni sono sequenziali: la seconda parte dopo la prima.
Questo è perfetto se recensioni dipende dal prodotto. Se invece due operazioni sono indipendenti, useremo async let o task group.
Sequenziale non significa sbagliato
Non trasformare tutto in parallelo.
func dettaglioProdotto(id: String) async throws -> DettaglioProdotto {
let prodotto = try await api.prodotto(id: id)
let disponibilita = try await api.disponibilita(per: prodotto.id)
return DettaglioProdotto(prodotto: prodotto, disponibilita: disponibilita)
}
Qui la disponibilità dipende dal prodotto. La sequenza è corretta.
async throws
async e throws si combinano bene:
func caricaCatalogo() async throws -> [Prodotto] {
let data = try await client.get("/products")
return try decoder.decode([Prodotto].self, from: data)
}
La chiamata usa try await:
do {
let prodotti = try await caricaCatalogo()
print(prodotti)
} catch {
print("Errore: \(error)")
}
L’ordine è try await, non await try.
Funzioni async e chiamanti
Una funzione che usa await deve essere a sua volta in un contesto asincrono:
func aggiornaCatalogo() async {
do {
let prodotti = try await caricaCatalogo()
print(prodotti)
} catch {
print(error)
}
}
Da codice sincrono puoi creare un Task, ma non abusarne:
Task {
await aggiornaCatalogo()
}
Il Task è una porta tra mondo sincrono e asincrono. Va usato nei confini, non ovunque.
URLSession
Con Foundation moderna:
func getData(from url: URL) async throws -> Data {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200..<300).contains(http.statusCode) else {
throw APIError.rispostaNonValida
}
return data
}
Il codice è lineare: chiamata, validazione, ritorno.
Non bloccare dentro async
Non usare Thread.sleep in codice asincrono. Blocca un thread.
Preferisci:
try await Task.sleep(for: .seconds(1))
Questa forma sospende il task.
Checklist
Ricorda:
asyncindica che una funzione può sospendersi;awaitmarca un punto di sospensione;awaitnon dovrebbe essere nascosto;try awaitcombina errori e asincronia;- sequenziale va bene quando c’è dipendenza;
- crea
Taskai confini tra sync e async; - non bloccare thread dentro funzioni async.
Esercizio
Scrivi:
func caricaProdotto(id: String) async throws -> Prodotto
func caricaRecensioni(prodottoID: String) async throws -> [Recensione]
func dettaglioProdotto(id: String) async throws -> DettaglioProdotto
La terza funzione deve chiamare le prime due in modo sequenziale, spiegando perché la seconda dipende dal risultato della prima.