Sobes.tech
Senior

Comment fonctionnent les mécanismes async et await en coulisses?

sobes.tech IA

Réponse de l'IA

Async/await en Swift est basé sur la concurrence structurée et utilise Dispatch/Global Actors. Les mécanismes sous-jacents sont :

  1. Style de continuation : Les fonctions asynchrones sont compilées en style de continuations. Au lieu de retourner immédiatement une valeur, le contrôle est transféré au runtime Swift, qui sauvegarde l'état actuel de la fonction (variables locales, pointeur d'exécution). Lorsque l'opération asynchrone se termine, le runtime reprend l'exécution de la fonction depuis l'état sauvegardé.

  2. Tâche : Chaque appel à une fonction async crée ou rejoint une Task structurée. La Task représente une unité de travail et forme une hiérarchie. La Task parent attend la fin des Task enfants.

  3. Job : La Task est divisée en unités de travail plus petites appelées Job. Un Job représente un fragment de code pouvant être exécuté sur un exécuteur.

  4. Exécuteur : L'exécuteur est responsable du lancement du Job. La bibliothèque standard utilise des DispatchQueue globales comme exécuteurs pour la plupart des tâches asynchrones. Les contextes spécifiques (par exemple, MainActor) ont leurs propres exécuteurs spécialisés.

  5. Points de suspension : await est un point de suspension. Lorsqu'il est atteint, la fonction est suspendue, le contrôle est transféré au code ou à l'exécuteur appelant, et le Job actuel se termine. Le runtime Swift sauvegarde l'état de la fonction.

  6. Reprise : Lorsque l'opération asynchrone attendue avec await se termine, le runtime Swift crée un nouveau Job pour continuer l'exécution de la fonction suspendue. Ce Job est mis en file d'attente sur l'exécuteur.

  7. Annulation : La concurrence structurée supporte l'annulation hiérarchique. L'annulation d'une Task parent annule automatiquement toutes ses Task enfants. Les opérations asynchrones peuvent vérifier l'état d'annulation et réagir en conséquence.

Voici un exemple simplifié de la compilation d'une fonction async :

// Fonction async d'origine
func fetchData() async -> Data {
    // Opération asynchrone
    let data = await URLSession.shared.data(from: url).0
    return data
}

// Représentation simplifiée de la compilation
// Au lieu de retourner, la fonction accepte une Continuation
func fetchData(_ continuation: Continuation<Data, Error>) {
    // Code initial de la fonction
    let url = URL(string: "...")! // Exemple
    
    // Démarrer l'opération asynchrone
    URLSession.shared.data(from: url) { data, _, error in
        // Cette clôture s'exécute après la fin de l'opération asynchrone
        if let data = data {
            // Passer le résultat à la continuation, en reprenant la fonction
            continuation.resume(returning: data)
        } else if let error = error {
            continuation.resume(throwing: error)
        } else {
            // Gestion d'autres cas
            continuation.resume(throwing: NSError(domain: "", code: 0, userInfo: nil))
        }
    }
    // La fonction se termine ici, en attendant l'appel à resume de la continuation
}

Await est essentiellement un sucre syntaxique sur la gestion de Continuation et la planification de Job sur l'exécuteur. La suspension ne bloque pas directement le fil, mais permet à l'exécuteur d'exécuter d'autres Job.