Sobes.tech
Senior

Jak działają mechanizmy async i await od kuchni?

sobes.tech AI

Odpowiedź od AI

Async/await w Swift opiera na oparciu o strukturalną współbieżność i korzysta z Dispatch/Global Actors. Mechanizmy działające pod maską to:

  1. Styl kontynuacji: Funkcje asynchroniczne są kompilowane w stylu kontynuacji. Zamiast od razu zwracać wartość, kontrola jest przekazywana do środowiska wykonawczego Swift, które zapisuje aktualny stan funkcji (zmienne lokalne, wskaźnik wykonania). Gdy operacja asynchroniczna się kończy, środowisko wykonawcze wznawia funkcję od zapisanego stanu.

  2. Zadanie: Każde wywołanie funkcji async tworzy lub dołącza do strukturalnego Task. Task reprezentuje jednostkę pracy i tworzy hierarchię. Zadanie nadrzędne oczekuje na zakończenie zadań podrzędnych.

  3. Job: Task jest dzielone na mniejsze jednostki pracy zwane Job. Job to fragment kodu, który może być wykonany na wykonawcy.

  4. Wykonawca: Odpowiada za uruchomienie Job. W bibliotece standardowej używa się globalnych DispatchQueue jako wykonawców dla większości zadań asynchronicznych. Konteksty specyficzne (np. MainActor) mają własnych wyspecjalizowanych wykonawców.

  5. Punkty zawieszenia: await jest punktem zawieszenia. Po osiągnięciu, funkcja jest zawieszana, kontrola jest przekazywana do wywołującego kodu lub wykonawcy, a bieżący Job kończy się. Środowisko wykonawcze Swift zapisuje stan funkcji.

  6. Wznowienie: Gdy operacja asynchroniczna oczekiwana przez await kończy się, środowisko wykonawcze Swift tworzy nowy Job do kontynuacji zawieszonej funkcji. Ten Job trafia do kolejki wykonawcy.

  7. Anulowanie: Strukturalna współbieżność obsługuje hierarchiczne anulowanie. Anulowanie nadrzędnego Task automatycznie anuluje wszystkie jego podrzędne Task. Operacje asynchroniczne mogą sprawdzać status anulowania i odpowiednio reagować.

Oto uproszczony przykład kompilacji funkcji async:

// Oryginalna funkcja async
func fetchData() async -> Data {
    // Operacja asynchroniczna
    let data = await URLSession.shared.data(from: url).0
    return data
}

// Uproszczona reprezentacja kompilacji
// Zamiast zwracać, funkcja przyjmuje Continuation
func fetchData(_ continuation: Continuation<Data, Error>) {
    // Kod początkowy funkcji
    let url = URL(string: "...")! // Przykład
    
    // Rozpoczęcie operacji asynchronicznej
    URLSession.shared.data(from: url) { data, _, error in
        // Ten blok jest wykonywany po zakończeniu operacji asynchronicznej
        if let data = data {
            // Przekazanie wyniku do continuation, wznowienie funkcji
            continuation.resume(returning: data)
        } else if let error = error {
            continuation.resume(throwing: error)
        } else {
            // Obsługa innych przypadków
            continuation.resume(throwing: NSError(domain: "", code: 0, userInfo: nil))
        }
    }
    // Funkcja kończy się tutaj, oczekując na wywołanie resume od continuation
}

Await jest w zasadzie cukrem składniowym nad zarządzaniem Continuation i planowaniem Job na wykonawcy. Zawieszenie nie blokuje bezpośrednio wątku, a pozwala wykonawcy realizować inne Job.