Sobes.tech
Senior

Hoe werken de mechanismen async en await onder de motorkap?

sobes.tech AI

Antwoord van AI

Async/await in Swift is gebaseerd op gestructureerde gelijktijdigheid en gebruikt Dispatch/Global Actors. De onderliggende mechanismen zijn:

  1. Continuatiestijl: Asynchrone functies worden gecompileerd in een continuatiestijl. In plaats van direct een waarde terug te geven, wordt de controle overgedragen aan de Swift-runtime, die de huidige staat van de functie (lokale variabelen, uitvoeringspointer) opslaat. Wanneer de asynchrone operatie voltooid is, hervat de runtime de uitvoering van de functie vanaf de opgeslagen staat.

  2. Taak: Elke aanroep van een async functie creëert of sluit zich aan bij een gestructureerde Task. De Task vertegenwoordigt een eenheid van werk en vormt een hiërarchie. De ouder-Task wacht op de voltooiing van de kind-Tasks.

  3. Job: De Task wordt opgedeeld in kleinere eenheden van werk, genaamd Job. Een Job vertegenwoordigt een stuk code dat op een executor kan worden uitgevoerd.

  4. Executor: De executor is verantwoordelijk voor het starten van een Job. In de standaardbibliotheek worden globale DispatchQueues gebruikt als executors voor de meeste asynchrone taken. Specifieke contexten (bijvoorbeeld MainActor) hebben hun eigen gespecialiseerde executors.

  5. Suspension points: await is een suspension point. Bij het bereiken wordt de functie gepauzeerd, wordt de controle overgedragen aan de aanroepende code of executor, en wordt de huidige Job beëindigd. De Swift-runtime slaat de staat van de functie op.

  6. Hervatting: Wanneer de asynchrone operatie waarop werd gewacht met await voltooid is, creëert de Swift-runtime een nieuwe Job om de uitvoering van de gepauzeerde functie voort te zetten. Deze Job wordt in de wachtrij van de executor geplaatst.

  7. Annulering: Gestructureerde gelijktijdigheid ondersteunt hiërarchische annulering. Het annuleren van een ouder-Task annuleert automatisch al zijn kind-Tasks. Asynchrone operaties kunnen de annuleringstatus controleren en hierop reageren.

Hier is een vereenvoudigd voorbeeld van de compilatie van een async functie:

// Oorspronkelijke async functie
func fetchData() async -> Data {
    // Asynchrone operatie
    let data = await URLSession.shared.data(from: url).0
    return data
}

// Vereenvoudigde representatie van de compilatie
// In plaats van te retourneren, accepteert de functie een Continuation
func fetchData(_ continuation: Continuation<Data, Error>) {
    // Initiële code van de functie
    let url = URL(string: "...")! // Voorbeeld
    
    // Start de asynchrone operatie
    URLSession.shared.data(from: url) { data, _, error in
        // Deze afsluiting wordt uitgevoerd na voltooiing van de asynchrone operatie
        if let data = data {
            // Geef het resultaat door aan de continuation, hervat de functie
            continuation.resume(returning: data)
        } else if let error = error {
            continuation.resume(throwing: error)
        } else {
            // Afhandeling van andere gevallen
            continuation.resume(throwing: NSError(domain: "", code: 0, userInfo: nil))
        }
    }
    // De functie eindigt hier, wachtend op de call van resume van de continuation
}

Await is in wezen suiker voor het beheer van Continuation en het plannen van Job op de executor. De pauzering blokkeert de thread niet direct, maar stelt de executor in staat om andere Jobs uit te voeren.