Async/Await: istota, funkcje async i praca z await

Autor: IT Sectr Opublikowano: 2026-03-16 Czas czytania: 8 min

Async/Await — to para kluczowych słów do pisania kodu asynchronicznego w stylu synchronicznym, dostępna w Swift od iOS 13 i w Kotlin z korutynami. Według Apple Swift Documentation, 2026, Async/Await zastępuje łańcuchy callbacków i GCD, czyniąc kod asynchroniczny liniowym i czytelnym. Słowo kluczowe async oznacza funkcję jako asynchroniczną, a await wstrzymuje jej wykonanie do momentu otrzymania wyniku.

Najważniejsze

  • Async/Await — syntaktyczny cukier dla kodu asynchronicznego bez zagnieżdżonych callbacków
  • async oznacza funkcję jako asynchroniczną i wstrzymywalną
  • await wstrzymuje wykonanie funkcji do zakończenia operacji asynchronicznej
  • Task w Swift tworzy nową asynchroniczną jednostkę pracy z kontekstem
  • Structured Concurrency gwarantuje, że wszystkie zadania potomne zakończą się przed rodzicielskim

Czym jest Async/Await i programowanie asynchroniczne

Async/Await — to konstrukcja językowa, która pozwala pisać kod asynchroniczny tak liniowo, jak synchroniczny. W Swift pojawiła się w iOS 13 / macOS 10.15 wraz z frameworkiem Swift Concurrency. W Kotlin async/await jest dostępny przez bibliotekę korutyn (kotlinx.coroutines) od wersji 1.3.

Przed pojawieniem się async/await kod asynchroniczny opierał się na callbackach, GCD lub RxSwift. Łańcuchy zagnieżdżonych callbacków prowadziły do callback hell — głębokiego zagnieżdżenia utrudniającego czytanie i debugowanie. Async/Await rozwiązuje ten problem, pozwalając funkcjom asynchronicznym „wstrzymywać się” w punktach await i wznawiać po otrzymaniu wyniku.

Według danych Google (2025), użycie async/await w Kotlin zmniejsza liczbę linii kodu asynchronicznego o 40–60% w porównaniu z podejściem callbackowym. W Swift adopcja Swift Concurrency osiągnęła 68% wśród opublikowanych aplikacji w 2025 roku. iOS 16 dodało obsługę Swift Concurrency w UIKit, SwiftUI i Foundation, co uczyniło async/await standardowym narzędziem dla wszystkich nowych projektów Apple.

Zasada działania: funkcje wstrzymywalne

Kluczowa różnica między async/await a wątkami — wstrzymanie bez blokowania. Gdy funkcja napotyka await, wstrzymuje się, zwalniając bieżący wątek dla innych zadań. Po zakończeniu operacji asynchronicznej funkcja wznawia się na tym samym lub innym wątku. Nazywa się to cooperative multitasking.

Jak działa Async/Await: wstrzymanie i wznowienie

Funkcja async jest kompilowana do maszyny stanów — automatu skończonego zarządzającego stanami wykonania. W punkcie await kompilator zapisuje kontekst (zmienne lokalne, adres powrotu) i przekazuje sterowanie do runtime. Gdy wynik jest gotowy, runtime przywraca kontekst i kontynuuje wykonanie.

W Swift ta maszyna stanów jest zaimplementowana na poziomie kompilatora SIL (Swift Intermediate Language). W Kotlin — poprzez mechanizm Continuation Passing Style (CPS): każda funkcja suspend przyjmuje ukryty parametr Continuation, przez który zwracany jest wynik. Oba podejścia gwarantują bezpieczne wznowienie bez wycieków pamięci.

  • Swift — kompilator transluje funkcję async do SIL z ramką runAsync coroutine
  • Kotlin — każda funkcja suspend otrzymuje Continuation i deleguje wykonanie do dyspozytora
  • Oba języki — zwalniają wątek przy await, nie blokując go na czas oczekiwania
  • Wyjątki — są propagowane standardowymi mechanizmami (throws / try-catch)

Async/Await vs Callbacki i GCD: porównanie podejść

Podejście callbackowe wymaga przekazania domknięcia, które jest wywoływane po zakończeniu operacji. Przy trzech kolejnych żądaniach powstają trzy poziomy zagnieżdżenia z obsługą błędów na każdym. Async/Await zamienia te same trzy żądania w trzy kolejne linie z jednym blokiem catch.

GCD (DispatchQueue) rozwiązuje problem callback hell poprzez kolejki serial i DispatchGroup, ale pozostaje rozwlekły. Dla prostego zadania — załadować user, potem friends, potem UI — wymaga tworzenia kolejek, grup i bloków notify. Async/Await wykonuje to samo zadanie trzema liniami w jednej funkcji.

AspektCallbackGCDAsync/Await
ZagnieżdżenieGłębokie (piramida zagłady)Średnie (grupy+notify)Liniowe (płaskie)
Obsługa błędówPer-callbackRęcznaJednolita (try/catch)
AnulowanieRęczneOgraniczoneTask.cancel()
Thread safetyRęcznaPrzez serial queuePrzez MainActor

Według Apple WWDC 2024, Swift Concurrency async/await to zalecane podejście dla nowego kodu. GCD pozostaje do integracji z bibliotekami C i specyficznych scenariuszy niskopoziomowego przetwarzania wątków.

Swift Concurrency: Task, TaskGroup i Actor

Task — to podstawowa jednostka pracy asynchronicznej w Swift. Task jest tworzony w kontekście istniejącego aktora lub na dowolnym wątku. Wewnątrz Task można wywoływać funkcje async przez await. TaskGroup pozwala uruchomić kilka zadań potomnych równolegle i zebrać ich wyniki — odpowiednik DispatchGroup na sterydach.

Actor — bezpieczny wątkowo typ referencyjny, chroniący swój stan przed wyścigami danych. Kompilator gwarantuje, że dostęp do właściwości aktora jest możliwy tylko przez wywołania async lub w ramach izolacji aktora. Actor zastępuje serial DispatchQueue do ochrony współdzielonego stanu modyfikowalnego bez ręcznych blokad.

Structured Concurrency

Zasada Structured Concurrency mówi: każde zadanie asynchroniczne ma rodzica, a rodzic nie kończy się, dopóki nie zakończą się wszystkie jego dzieci. Jeśli zadanie A uruchamia Task { await B() }, to A oczekuje na B. Zapobiega to wyciekom zadań i zapewnia przewidywalny cykl życia.

Async/Await w Kotlin z korutynami

W Kotlin async/await jest zaimplementowany przez coroutines — lekkie wątki języka. Funkcja jest oznaczana słowem kluczowym suspend (odpowiednik Swift async). Do uruchomienia używa się launch (fire-and-forget) lub async (z wynikiem). Await jest wywoływany w celu uzyskania wyniku z async.

Dispatcher określa, na której puli wątków wykonywana jest korutyna: Dispatchers.Main dla UI, Dispatchers.IO dla sieci/dysku i Dispatchers.Default dla zadań intensywnych obliczeniowo. Structured Concurrency jest zapewniana przez CoroutineScope — po anulowaniu scope anulowane są wszystkie korutyny potomne.

Według danych JetBrains (2025), 97% aplikacji Android w Google Play używa kotlinx.coroutines, a 82% z nich stosuje async/await do zapytań sieciowych. Coroutines stały się standardem de facto dla programowania asynchronicznego w Android. Kotlin Multiplatform również wspiera korutyny, umożliwiając używanie wspólnego kodu asynchronicznego na Android, iOS i po stronie serwera.

Przykłady kodu z Async/Await w Swift i Kotlin

Rozważmy po dwa przykłady dla każdego języka: sekwencyjne ładowanie danych i równoległe żądania z TaskGroup lub async/await.

Swift: sekwencyjne wywołania async

await wstrzymuje wykonanie do momentu pobrania danych użytkownika, następnie — do pobrania znajomych. Żadnych zagnieżdżonych domknięć.

swift
func loadProfile() async throws -> Profile {
    let user = try await api.fetchUser()
    let friends = try await api.fetchFriends(for: user.id)
    return Profile(user: user, friends: friends)
}

Swift: równoległe żądania z TaskGroup

TaskGroup uruchamia zadania równolegle i zbiera wyniki w tablicy. Kolejność wyników może nie pokrywać się z kolejnością uruchamiania.

swift
func loadParallel() async throws -> [String] {
    await withThrowingTaskGroup(of: String.self) { group in
        group.addTask { try await api.fetchName() }
        group.addTask { try await api.fetchEmail() }
        group.addTask { try await api.fetchAvatar() }
        var results = [String]()
        for try await value in group { results.append(value) }
        return results
    }
}

Kotlin: sekwencyjne wywołanie z async/await

Funkcje suspend mogą wywoływać inne funkcje suspend. Odpowiednik Swift: sekwencyjne wywołania.

kotlin
suspend fun loadProfile(): Profile {
    val user = api.fetchUser()
    val friends = api.fetchFriends(user.id)
    return Profile(user, friends)
}

Kotlin: równoległe żądania z async/await

W Kotlin równoległość osiąga się przez coroutineScope z async dla każdego żądania.

kotlin
suspend fun loadParallel(): List<String> = coroutineScope {
    val name = async { api.fetchName() }
    val email = async { api.fetchEmail() }
    val avatar = async { api.fetchAvatar() }
    listOf(name.await(), email.await(), avatar.await())
}

Typowe błędy i ograniczenia Async/Await

Zapomniany Task { } — funkcji asynchronicznej nie można wywołać z kontekstu synchronicznego bez opakowania w Task. W Swift skutkuje to błędem kompilacji. W Kotlin wymagane jest uruchomienie przez lifecycleScope lub viewModelScope. Próba wywołania funkcji suspend ze zwykłej funkcji nie skompiluje się.

Nadmierne używanie async bez potrzeby

Nie każdą funkcję trzeba czynić asynchroniczną. Zadania intensywne obliczeniowo bez I/O nie zyskują na async — lepiej wykonywać je na DispatchQueue z .userInitiated QoS. Async/Await jest zoptymalizowany dla operacji I/O-bound: sieć, dysk, oczekiwanie.

Blokowanie głównego wątku przez .result

Użycie .result na głównym wątku (synchroniczne oczekiwanie na wynik funkcji async) prowadzi do blokowania. W Swift Task.synchronousWait nie jest zalecane przez Apple. W Kotlin runBlocking na głównym wątku to antypattern — używaj lifecycleScope.

Wyciek Task przy anulowaniu

Jeśli Task nie jest zapisany we właściwości klasy, po wyjściu z kontekstu zostaje anulowany. W Swift Task jest scoped — jego cykl życia jest powiązany z kontekstem tworzącym. Zachowuj Task we właściwości przy potrzebie długotrwałej operacji.

Często zadawane pytania

Czym async/await różni się od DispatchQueue?

Async/Await — to konstrukcja językowa, niewymagająca tworzenia kolejek. DispatchQueue — to systemowe API do zarządzania wątkami. Async/await jest kompilowany do maszyny stanów na poziomie języka, GCD — do syscall na poziomie systemu operacyjnego.

Czy await blokuje wątek?

Nie — await wstrzymuje funkcję, ale zwalnia wątek dla innych zadań. Po zakończeniu operacji funkcja wznawia się na dowolnym dostępnym wątku z puli.

Czy można anulować zadanie async?

Tak — przez Task.cancel() w Swift lub Job.cancel() w Kotlin. Anulowanie rozprzestrzenia się na wszystkie zadania potomne zgodnie z Structured Concurrency. Kod sam sprawdza flagę anulowania przez Task.isCancelled lub ensureActive().

Czym jest MainActor w Swift?

MainActor — to aktor, którego właściwości i metody są zawsze wykonywane na głównym wątku. Oznaczając funkcję @MainActor, gwarantujesz, że aktualizacje UI odbywają się na właściwym wątku, zastępując DispatchQueue.main.async.

W jakich językach jest async/await?

Async/Await jest dostępny w Swift (iOS 13+), Kotlin (przez kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust i Go (przez goroutines z syntaktycznym cukrem).

Podsumowanie

  • Async/Await — konstrukcja językowa do liniowego kodu asynchronicznego bez callback hell
  • Swift Concurrency zapewnia Task, TaskGroup, Actor i Structured Concurrency
  • Kotlin coroutines używają funkcji suspend, Dispatchers i zakresów korutyn
  • Wstrzymanie bez blokowania — kluczowa różnica async/await od wątków i kolejek
  • Structured Concurrency gwarantuje zakończenie wszystkich zadań potomnych przed rodzicielskim
  • Async/Await vs GCD — pierwsze preferowane dla nowego kodu, drugie dla legacy i niskiego poziomu
  • MainActor w Swift i Dispatchers.Main w Kotlin chronią wątek UI przed operacjami w tle

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również