Sobes.tech
Senior

¿Cómo funcionan los mecanismos async y await por debajo del capó?

sobes.tech AI

Respuesta de la IA

Async/await en Swift se basa en la concurrencia estructurada y utiliza Dispatch/Global Actors. Los mecanismos que trabajan detrás de escena son:

  1. Estilo de Continuación: Las funciones asíncronas se compilan en un estilo de continuaciones. En lugar de devolver inmediatamente un valor, el control se transfiere al runtime de Swift, que guarda el estado actual de la función (variables locales, puntero de ejecución). Cuando la operación asíncrona termina, el runtime reanuda la ejecución de la función desde el estado guardado.

  2. Tarea: Cada llamada a una función async crea o se une a una Task estructurada. La Task representa una unidad de trabajo y forma una jerarquía. La Task padre espera a que las tareas hijas terminen.

  3. Trabajo: La Task se divide en unidades de trabajo más pequeñas llamadas Job. Un Job representa un fragmento de código que puede ser ejecutado en un ejecutor.

  4. Ejecutor: El ejecutor es responsable de lanzar el Job. En la biblioteca estándar, se usan DispatchQueue globales como ejecutores para la mayoría de las tareas asíncronas. Los contextos específicos (por ejemplo, MainActor) tienen sus propios ejecutores especializados.

  5. Puntos de suspensión: await es un punto de suspensión. Cuando se alcanza un await, la función se suspende, el control se transfiere al código o ejecutor que llamó, y el Job actual termina. El runtime de Swift guarda el estado de la función.

  6. Reanudación: Cuando la operación asíncrona en la que se esperaba con await termina, el runtime de Swift crea un nuevo Job para continuar la ejecución de la función suspendida. Este Job se pone en la cola del ejecutor.

  7. Cancelación: La concurrencia estructurada soporta cancelación jerárquica. La cancelación de una Task padre cancela automáticamente todas sus Task hijas. Las operaciones asíncronas pueden verificar el estado de cancelación y reaccionar en consecuencia.

Aquí un ejemplo simplificado de la compilación de una función async:

// Función async original
func fetchData() async -> Data {
    // Operación asíncrona
    let data = await URLSession.shared.data(from: url).0
    return data
}

// Representación simplificada de la compilación
// En lugar de devolver, la función recibe una Continuation
func fetchData(_ continuation: Continuation<Data, Error>) {
    // Código inicial de la función
    let url = URL(string: "...")! // Ejemplo
    
    // Iniciar operación asíncrona
    URLSession.shared.data(from: url) { data, _, error in
        // Este cierre se ejecuta después de que la operación asíncrona termina
        if let data = data {
            // Pasar el resultado a la continuation, reanudando la función
            continuation.resume(returning: data)
        } else if let error = error {
            continuation.resume(throwing: error)
        } else {
            // Manejo de otros casos
            continuation.resume(throwing: NSError(domain: "", code: 0, userInfo: nil))
        }
    }
    // La función termina aquí, esperando que la llamada a resume de la continuation
}

Await es básicamente azúcar sintáctico sobre la gestión de Continuation y la planificación de Job en el ejecutor. La suspensión no bloquea directamente el hilo, sino que permite que el ejecutor ejecute otros Job.