Sobes.tech
Senior

Como funcionam os mecanismos async e await por baixo do capô?

sobes.tech IA

Resposta da IA

Async/await em Swift baseia-se na concorrência estruturada e utiliza Dispatch/Global Actors. Os mecanismos subjacentes são:

  1. Estilo de Continuação: As funções assíncronas são compiladas em estilo de continuações. Em vez de retornarem imediatamente um valor, o controlo é transferido para o runtime Swift, que guarda o estado atual da função (variáveis locais, ponteiro de execução). Quando a operação assíncrona termina, o runtime retoma a execução da função a partir do estado guardado.

  2. Tarefa: Cada chamada a uma função async cria ou junta-se a uma Task estruturada. A Task representa uma unidade de trabalho e forma uma hierarquia. A Task pai espera que as Task filhas terminem.

  3. Job: A Task é dividida em unidades de trabalho mais pequenas chamadas Job. Um Job representa um fragmento de código que pode ser executado num executor.

  4. Executor: O executor é responsável por lançar o Job. Na biblioteca padrão, usam-se DispatchQueue globais como executores para a maioria das tarefas assíncronas. Os contextos específicos (por exemplo, MainActor) têm os seus próprios executores especializados.

  5. Pontos de suspensão: await é um ponto de suspensão. Quando atingido, a função é suspensa, o controlo é transferido para o código ou executor que chamou, e o Job atual termina. O runtime Swift guarda o estado da função.

  6. Reinício: Quando a operação assíncrona esperada com await termina, o runtime Swift cria um novo Job para continuar a execução da função suspensa. Este Job é colocado na fila do executor.

  7. Cancelamento: A concorrência estruturada suporta cancelamento hierárquico. O cancelamento de uma Task pai cancela automaticamente todas as suas Task filhas. As operações assíncronas podem verificar o estado de cancelamento e reagir em conformidade.

Aqui está um exemplo simplificado da compilação de uma função async:

// Função async original
func fetchData() async -> Data {
    // Operação assíncrona
    let data = await URLSession.shared.data(from: url).0
    return data
}

// Representação simplificada da compilação
// Em vez de retornar, a função aceita uma Continuação
func fetchData(_ continuation: Continuation<Data, Error>) {
    // Código inicial da função
    let url = URL(string: "...")! // Exemplo
    
    // Iniciar operação assíncrona
    URLSession.shared.data(from: url) { data, _, error in
        // Este encerramento é executado após a operação assíncrona terminar
        if let data = data {
            // Passar o resultado para a continuação, retomando a função
            continuation.resume(returning: data)
        } else if let error = error {
            continuation.resume(throwing: error)
        } else {
            // Tratamento de outros casos
            continuation.resume(throwing: NSError(domain: "", code: 0, userInfo: nil))
        }
    }
    // A função termina aqui, aguardando que a chamada a resume da continuação
}

Await é essencialmente açúcar sintático sobre a gestão de Continuation e o planeamento de Job no executor. A suspensão não bloqueia diretamente o thread, mas permite que o executor execute outros Job.