BE/Kotlin

[Kotlin] 비동기 처리 (async, await)

baek-dev 2025. 3. 29. 14:01

코틀린의 비동기 처리 (async, await)

 

코틀린에서는 비동기(Asynchronous) 처리를 위해 Coroutine(코루틴)을 활용함.

특히, async와 await 을 사용하면 비동기 작업을 실행하고 그 결과를 기다리는 방식으로 코드를 효율적으로 작성할 수 있음.

 


1️⃣ 비동기 처리의 필요성

 

코틀린에서 비동기 처리를 사용하는 이유:

CPU 작업과 I/O 작업을 효율적으로 처리하기 위해

동기 코드보다 빠른 실행 가능 (여러 작업을 동시에 수행)

메인 스레드를 블로킹(blocking)하지 않음 (예: UI 애플리케이션에서 중요)

 


2️⃣ async 와 await 개념

async {}비동기적으로 실행되는 작업을 정의

await()비동기 작업의 결과를 반환받음 (suspend 함수에서만 사용 가능)

 

async 는 비동기적으로 실행되는 Deferred<T> (미래의 값을 담는 객체) 를 반환함.

즉, async를 사용하면 결과를 즉시 기다리지 않고 다른 작업을 수행할 수 있음.

 


3️⃣ async & await 기본 사용법

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred: Deferred<String> = async {
        delay(2000)  // 2초 대기 (비동기 처리)
        "Hello, Kotlin!"
    }

    println("비동기 작업 실행 중...")
    val result = deferred.await() // 결과를 기다림
    println("결과: $result")
}

 

출력 결과

비동기 작업 실행 중...
(2초 후)
결과: Hello, Kotlin!

 

비동기 실행:

async 내부에서 delay(2000) 동안 대기하면서, 메인 스레드는 블로킹되지 않음.

await()을 호출하면 결과를 기다린 후 반환함.

 


4️⃣ async를 활용한 병렬 실행

 

async를 여러 개 사용하면 여러 작업을 병렬로 실행할 수 있음.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val time = measureTimeMillis {
        val task1 = async { fetchData1() }
        val task2 = async { fetchData2() }

        println("결과: ${task1.await()} + ${task2.await()}")
    }
    println("총 실행 시간: $time ms")
}

suspend fun fetchData1(): String {
    delay(2000) // 네트워크 요청 시뮬레이션
    return "데이터1"
}

suspend fun fetchData2(): String {
    delay(3000) // 다른 요청
    return "데이터2"
}

 

출력 결과

결과: 데이터1 + 데이터2
총 실행 시간: 3000 ms

 

병렬 실행 효과:

fetchData1()fetchData2()async 덕분에 동시에 실행됨.

전체 실행 시간이 2초 + 3초 = 5초가 아니라 3초만 소요됨 (병렬 처리 덕분).

 


5️⃣ async와 launch의 차이점

비교 항목 async launch
반환값 Deferred<T> (결과를 반환) Job (결과 없음)
사용 목적 비동기 연산 후 결과 반환 단순한 비동기 실행
await() 사용 가능 여부 ✅ 가능 (await()로 결과 반환) ❌ 사용 불가
예제 async { fetchData() } launch { doSomething() }

 

예제 비교

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch { 
        println("launch 실행") 
    }
    
    val deferred = async { 
        "async 결과 반환" 
    }

    println("async 결과: ${deferred.await()}") // ✅ 가능
    // println("launch 결과: ${job.await()}") // ❌ 에러 발생 (launch는 await 불가)
}

 

정리:

launch → 단순 실행용 (결과가 필요 없을 때)

async → 비동기적으로 실행하고 결과를 받아야 할 때

 


6️⃣ async에서 예외 처리 (try-catch)

 

비동기 코드에서 예외가 발생하면 처리해줘야 함.

예외가 발생하면 await()를 호출하는 시점에서 예외가 던져짐.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred = async {
        try {
            delay(1000)
            throw Exception("에러 발생!")
        } catch (e: Exception) {
            println("비동기 예외 처리: ${e.message}")
            "기본값 반환"
        }
    }

    println("결과: ${deferred.await()}")
}

 

출력 결과

비동기 예외 처리: 에러 발생!
결과: 기본값 반환

 

예외 처리 가능!

try-catchasync 내부에서 사용하여 예외가 발생해도 안전하게 처리할 수 있음.

 


7️⃣ supervisorScope를 활용한 예외 처리

기본적으로 async한 개의 예외가 발생하면 전체 코루틴이 중단됨.

supervisorScope를 사용하면 하나의 코루틴이 실패해도 다른 코루틴이 계속 실행됨.

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        val task1 = async {
            delay(1000)
            throw Exception("task1 실패!")
        }

        val task2 = async {
            delay(2000)
            "task2 성공!"
        }

        try {
            println("결과: ${task1.await()} + ${task2.await()}")
        } catch (e: Exception) {
            println("예외 발생: ${e.message}")
        }
    }
}

 

출력 결과

예외 발생: task1 실패!

 

task1은 실패했지만, task2는 계속 실행됨.

 


8️⃣ 정리

async → 비동기 작업 실행 (결과 반환 가능)

await() → 비동기 작업의 결과를 기다림

여러 개의 async를 사용하면 병렬 처리 가능

예외 처리는 try-catch 또는 supervisorScope를 활용

비동기 실행만 하고 결과가 필요 없으면 launch 사용

 

 

 

 

출처 : ChatGPT