본문 바로가기
개발새발/코틀린

[코틀린/Kotlin] 코루틴(Coroutine)

by 조희우 2025. 8. 28.

코루틴 기초

  • 가벼운 비동기 처리 방식
  • 기존의 Thread, Callback 기반보다 간결하고 안전하게 비동기 작업 처리 가능

코루틴 사용 이유

  • 복잡한 콜백 지옥(callback hell) 제거
  • UI 스레드 방지
  • 동기식처럼 읽히는 비동기 처리
  • 네트워크 통신, 데이터베이스 작업 등에 많이 사용

기본 예시

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}
Hello,
World!

주요 키워드

키워드 설명

suspend 일시 중단 가능한 함수에 사용
launch 새 코루틴을 실행 (결과 X)
async 결과를 반환하는 코루틴 (Deferred)
runBlocking main 함수나 테스트에서 사용 (코루틴 시작점)
delay 일시 중단 함수 (non-blocking sleep)

CorountineScope

  • 코루틴을 실행할 수 있는 범위가 제공
  • 스코프가 사라지면 해당 스코프 내의 코루틴들도 모두 취소
CoroutineScope(Dispathers.Main).launch {
	...
}

withContext

  • 일시적으로 스레드를 전환
  • 예시: IO 작업은 Dispatcher.IO에서 UI 작업은 Dispatchers.Main에서 작업할 경우
withContext(Dispatchers.IO) { val data = api.fetch() }

suspend

  • 일시 중단이 가능한 함수에 붙이는 키워드
  • 보통 코루틴 내부에서만 호출 가능
suspend fun getUserData(): String {
    delay(1000)
    return "희우!"
}

launch

  • 새로운 코루틴을 실행
  • 반환값은 없음 (Job을 리턴)Job
더보기
더보기
더보기

📌 Job

코루틴의 생명주기를 관리하는 객체: 이 작업의 상태

  • 상태 확인: isActive, isCompleted, isCancelled
  • 취소: job.cancel()
  • 완료 대기: job.join()
  • 비동기 작업을 수행하고 결과는 필요 없을 때 사용
scope.launch { 
	delay(1000)
	println("1초 후 출력")
}

async/await

  • 코루틴을 시작하고 결과를 반환
  • Deferred 객체를 반환
더보기
더보기
더보기

📌 Deferred<T>

Job의 하위 클래스이자 비동기 연산의 결과값

비동기 작업의 결과를 나중에 맏을 수 있는 객체

  • await()을 통해 async의 결과값을 가져옴
val result = async {
	getUserData()
}
println(result.await())

runBlocking

  • 일반 함수에서 코루틴을 호출
  • 현재 스레드를 차단
  • 주의: 실제 작업보다는 테스트나 학습용으로 사용
fun main() = runBlocking {
	launch {
		delay(1000)
		println("Hello from coroutine")
	}
}

delay

  • 코루틴을 일정 시간 동안 일시 중단
  • Thread.sleep()과 달리 스레드를 차단하지 않음
delay(1000)

문제

서술형

1. 다음 main 함수가 "Hello," 출력 후 1초 뒤에 "Coroutine!"을 출력하도록 launch와 delay를 이용해 코드를 구현하시오.

fun main() = runBlocking {
    // TODO: 코루틴을 launch로 실행하고, delay를 이용해서 "Coroutine!"을 1초 뒤에 출력
    println("Hello,")
}

 

답:

fun main() = runBlocking {
  println("Hello,")
	launch{
		delay(1000)
		println("Coroutin!")
	}
}

 

보충:

  • runBlocking 블럭이 launch보다 먼저 종료되어 “Coroutin!”이 출력되지 않고 종료될 수 있음
  • → launch뒤에 join()을 붙여서 해당 코루틴이 끝날 때까지 기다리기
  • join() 사용 이유
    • launch는 비동기 실행이기 때문에, main이 먼저 끝나면 코루틴이 출력 전에 사라질 수 있음
    • join()을 사용하여 launch한 코루틴이 끝날 때까지 main 스레드를 블럭화
fun main() = runBlocking {
    println("Hello,")
    val job = launch {
        delay(1000)
        println("Coroutine!")
    }
    job.join() // 코루틴이 끝날 때까지 기다림
}

 

 

2. 다음 코드의 실행 결과를 예측하고 어떤 차이가 있는지 설명해보시오.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        delay(1000L)
        println("launch 안의 코루틴")
    }

    val deferred = async {
        delay(1000L)
        "async 안의 결과"
    }

    println("메인 함수 대기 중")
    job.join()
    val result = deferred.await()
    println("async 결과: $result")
}

 

답:

메인 함수 대기 중

launch 안의 코루틴

async 결과: async 안의 결과

 

우선 main 함수에서 lauch와 async를 사용 후 대기되며 대기 중 “메인 함수 대기 중”이 출력된다.

launch는 job.join()으로 결과값을 대기 한 후 출력된다.

async도 launch와 같이 1000L만큼 delay되지만 await()으로 result에 호출될때까지 기다린다.

result값을 호출되고 반환값이 함께 출력된다.

 

 

3. 아래 코드에서 Job을 이용해 코루틴을 중간에 취소하고, 결과를 확인해보세요.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(5) { i ->
            println("코루틴 실행 중... $i")
            delay(500L)
        }
    }

    delay(1200L)
    println("코루틴 취소!")
    job.cancel()
    println("메인 종료")
}

답:

코루틴 실행 중 … 0

코루틴 실행 중 … 1

코루틴 실행 중 … 2

코루틴 취소!

메인 종료

 

job.cancel()로 인하여 launch 내부의 repea(5)는 모두 실행되지 못하고 delay(1200L) 후 종료된다.

 

 

3. async를 사용해서 두 개의 코루틴 작업을 동시에 실행하고, 결과를 더해 출력하시오.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred1 = async {
        delay(1000L)
        10
    }

    val deferred2 = async {
        delay(1000L)
        20
    }

    val sum = deferred1.await() + deferred2.await()
    println("합계: $sum")
}

3-1. 총 실행 시간은 몇 초일까요?

 

답: 1000L, 비동기 실행으로 동시에 실행된다.

 

3-2. await()을 쓰지 않으면 어떻게 될까요?

 

답: 값을 반환받지 못한다. 작업을 실행되지만 출력을 할 수 없다.

 

실습형

1. async와 await의 동작 확인하기

다음 조건을 만족하는 코드를 직접 작성해보세요.

  • runBlocking 블록 안에서,
  • async를 사용해 1초 후 "Hello"를 반환하는 코루틴과
  • 2초 후 "World"를 반환하는 코루틴을 각각 작성하세요.
  • 두 결과를 await하여 "Hello World"를 출력해보세요.
  • 총 실행 시간은 약 2초 정도가 되도록 작성하세요.

[힌트]

  • delay()와 await()을 적절히 사용하면 병렬적으로 실행되게 만들 수 있어요.
import kotlin.coroutines.*

fun main() = runBlocking {
	val hello = async {
		delay(1000L)
		"Hello"
	}
	
	val world = async {
		delay(2000L)
		"World"	
	}
	
	val greeting = hello.await() + " " + world.await()
	println(greeting)
}

 

 

2. 여러 코루틴 동시 실행과 순서 제어

아래 조건에 맞는 코틀린 코루틴 코드를 작성

  1. runBlocking 내에서 3개의 코루틴을 async로 실행한다.
  2. 각각 코루틴은 서로 다른 시간(1초, 2초, 3초)만큼 delay 후에 자신의 인덱스(1, 2, 3)를 문자열로 반환한다.
  3. 모든 코루틴 결과를 기다린 뒤(모두 완료될 때까지) 세 개의 결과를 순서대로 합쳐서 출력한다.
  4. 출력 형식은 "Results: 1 2 3" 이다.

[힌트]

  • await()를 활용하고, async로 코루틴을 시작하는 즉시 실행을 시작할 수 있어.
import kotlin.coroutines.*

fun main() = runBlocking {
	val first = async {
		delay(1000L)
		1
	}
	
	val second = async {
		delay(2000L)
		2
	}
	
	val third = async {
		delay(3000L)
		3
	}
	
	val count = "Results: " + first.await() + " " + second.await() + " " + third.await()
	println(count)
}

 

 

3. 코루틴 취소 (Coroutine Cancellation) 실습

아래 조건에 맞춰 코드를 작성

  • runBlocking 내에서 launch로 코루틴을 실행한다.
  • 코루틴은 5번에 걸쳐 500ms씩 delay하며 "작업 중: i" 를 출력한다. (i는 0~4)
  • 1200ms 뒤에 코루틴을 취소(cancel)한다.
  • 코루틴이 취소되면 "코루틴이 취소되었습니다."를 출력하도록 처리한다.

 

답: 

import kotlin.coroutine.*

fun main() = runBlocking {
	val job = launch {
		repeat(5) {
			delay(500L)
			println("작업 중: $i")
		}
	}
	
	delay(1200L)
	job.cancel()
	job.join()
	println("코루틴이 취소되었습니다.")
}

 

보충: cancelAndJoin()을 사용하는 방법이 있음

  • 코루틴을 취소하고 코루틴이 완전히 종료될때까지 대기
  • cancel() + join() → cancelAndJoin()