티스토리 뷰

728x90

4. 비동기 흐름 제어: suspend 함수와 withContext

4.1 suspend 함수란?

suspend는 코틀린에서 비동기 작업을 정의할 때 사용되는 키워드입니다. suspend 함수는 일시 중단될 수 있는 함수로, 실행 중간에 일시적으로 멈췄다가 다른 작업을 처리한 후 다시 재개될 수 있습니다. 일반 함수와는 달리 코루틴 내에서만 호출될 수 있으며, 비동기 작업을 처리하는 데 최적화되어 있습니다.

suspend 함수는 동기적 코드처럼 작성하지만, 실제로는 비동기적으로 실행되어 코드 가독성을 크게 향상시킵니다.

suspend 함수의 특징:

  • 코루틴 내에서만 호출될 수 있습니다.
  • 코루틴 내에서 호출된 suspend 함수는 일시적으로 중단되었다가 재개될 수 있습니다.
  • 비동기 흐름 제어를 동기적 코드처럼 간단하고 직관적으로 작성할 수 있습니다.

suspend 함수의 기본 사용 예제

import kotlinx.coroutines.*

// suspend 키워드를 사용해 비동기 함수를 정의
suspend fun performTask() {
    delay(1000L)  // 비동기 지연
    println("Task completed")
}

fun main() = runBlocking {
    performTask()  // suspend 함수는 코루틴 내에서만 호출 가능
    println("Main program ends")
}

설명:

  • performTask 함수는 suspend 키워드를 사용하여 비동기 작업을 정의했습니다.
  • 이 함수는 코루틴 내에서만 호출 가능하므로, runBlocking 내부에서 호출하여 비동기 작업을 수행합니다.
  • delay코루틴을 일시적으로 중단시킨 후 1초 뒤에 재개됩니다.

출력:

Task completed
Main program ends

4.2 withContext란?

withContext다른 디스패처(Dispatcher)로 컨텍스트를 변경하면서 특정 블록의 코드를 실행하는 코루틴 빌더입니다. 이를 통해, 입출력 작업(I/O)이나 CPU 집약적인 작업을 다른 디스패처에서 처리하고, 메인 스레드의 부하를 줄일 수 있습니다.

withContext의 특징:

  • 코루틴의 컨텍스트를 변경하여 특정 블록의 코드를 다른 디스패처에서 실행할 수 있습니다.
  • 주로 입출력 작업(Dispatchers.IO), CPU 집중 작업(Dispatchers.Default), UI 작업(Dispatchers.Main)을 구분할 때 사용됩니다.
  • 코드를 일시 중단한 후, 다른 컨텍스트에서 재개할 수 있습니다.

withContext 예제

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Main program starts: ${Thread.currentThread().name}")

    // withContext를 사용해 디스패처를 변경
    val result = withContext(Dispatchers.IO) {
        println("Performing I/O operation on: ${Thread.currentThread().name}")
        delay(1000L)  // I/O 작업을 시뮬레이션
        "Result of I/O operation"
    }

    println("Received result: $result")
    println("Main program ends: ${Thread.currentThread().name}")
}

설명:

  • withContext는 코드 블록을 Dispatchers.IO에서 실행하도록 하여, 입출력 작업을 별도의 스레드에서 처리할 수 있습니다.
  • 코드 블록이 완료되면 결과값을 반환하고, 원래의 컨텍스트로 돌아가서 나머지 코드를 실행합니다.
  • 이 코드는 메인 스레드에서 시작하지만, 입출력 작업별도의 I/O 스레드에서 처리됩니다.

출력:

Main program starts: main
Performing I/O operation on: DefaultDispatcher-worker-1
Received result: Result of I/O operation
Main program ends: main
  • 메인 프로그램은 메인 스레드에서 시작하지만, 입출력 작업Dispatchers.IO에서 수행되며, 별도의 스레드(DefaultDispatcher-worker-1)에서 실행됩니다.
  • 입출력 작업이 완료된 후, 결과값을 받아 메인 스레드에서 출력됩니다.

4.3 suspend 함수와 withContext 결합

suspend 함수withContext를 결합하여 효율적인 비동기 작업을 처리할 수 있습니다. 입출력 작업이나 네트워크 작업을 별도의 스레드에서 처리하고, 그 작업이 끝나면 메인 스레드로 돌아와서 UI를 업데이트하는 방식으로 사용됩니다.

예제: suspendwithContext 결합

import kotlinx.coroutines.*

// suspend 함수로 네트워크 작업 시뮬레이션
suspend fun fetchData(): String {
    return withContext(Dispatchers.IO) {  // I/O 스레드에서 실행
        println("Fetching data on: ${Thread.currentThread().name}")
        delay(1000L)  // 네트워크 지연 시뮬레이션
        "Data from server"
    }
}

fun main() = runBlocking {
    println("Main program starts: ${Thread.currentThread().name}")

    // suspend 함수 호출
    val data = fetchData()
    println("Received data: $data")

    println("Main program ends: ${Thread.currentThread().name}")
}

설명:

  • fetchData 함수는 네트워크 작업을 비동기적으로 시뮬레이션하며, 이 작업은 Dispatchers.IO에서 실행됩니다.
  • fetchData 함수가 완료되면 데이터가 반환되고, 메인 스레드에서 결과를 처리할 수 있습니다.

출력:

Main program starts: main
Fetching data on: DefaultDispatcher-worker-1
Received data: Data from server
Main program ends: main
  • 입출력 작업별도의 I/O 스레드에서 실행되고, 네트워크 작업이 완료된 후 메인 스레드로 돌아와 데이터를 처리합니다.

4.4 withContext의 주요 활용 사례

withContext는 주로 입출력 작업이나 CPU 집약적인 작업별도의 스레드에서 처리하고, 그 후에 메인 스레드로 돌아와 결과를 처리할 때 유용합니다.

  1. 네트워크 작업 처리:
    • 네트워크 요청이나 파일 입출력과 같은 작업은 Dispatchers.IO에서 처리하여, 메인 스레드의 부하를 줄입니다.
  2. CPU 집약적 작업 처리:
    • 데이터 처리나 계산 작업과 같은 CPU 집약적 작업은 Dispatchers.Default에서 처리하여, 다른 작업에 영향을 주지 않도록 합니다.
  3. UI 업데이트:
    • 안드로이드나 UI 환경에서는 Dispatchers.Main을 사용하여 UI 업데이트 작업을 메인 스레드에서 수행합니다.

4.5 suspendwithContext를 활용한 실제 프로젝트 구성

다음은 간단한 네트워크 요청 시뮬레이션을 통해 suspend 함수와 withContext를 활용하여 비동기적으로 데이터를 처리하는 방식입니다.

예제: 네트워크 데이터 처리 시뮬레이션

import kotlinx.coroutines.*

// suspend 함수로 데이터를 가져오는 함수
suspend fun fetchDataFromNetwork(): String {
    return withContext(Dispatchers.IO) {
        println("Fetching data on: ${Thread.currentThread().name}")
        delay(2000L)  // 네트워크 지연 시뮬레이션
        "Data from network"
    }
}

// suspend 함수로 데이터를 처리하는 함수
suspend fun processData(data: String) {
    withContext(Dispatchers.Default) {
        println("Processing data on: ${Thread.currentThread().name}")
        delay(1000L)  // 데이터 처리 시뮬레이션
        println("Data processed: $data")
    }
}

fun main() = runBlocking {
    println("Main program starts: ${Thread.currentThread().name}")

    // 네트워크에서 데이터를 가져오고 처리
    val data = fetchDataFromNetwork

()
    processData(data)

    println("Main program ends: ${Thread.currentThread().name}")
}

설명:

  1. fetchDataFromNetwork 함수는 네트워크에서 데이터를 가져오는 작업을 시뮬레이션하며, 입출력 작업Dispatchers.IO에서 처리됩니다.
  2. processData 함수는 데이터를 처리하는 작업을 CPU 집약적인 작업으로 시뮬레이션하며, Dispatchers.Default에서 처리됩니다.
  3. 두 함수는 모두 비동기적으로 실행되며, withContext를 통해 스레드 간의 작업 분배가 이루어집니다.

출력:

Main program starts: main
Fetching data on: DefaultDispatcher-worker-1
Processing data on: DefaultDispatcher-worker-2
Data processed: Data from network
Main program ends: main
  • 네트워크 작업은 I/O 스레드에서 실행되고, 데이터 처리 작업은 CPU 스레드에서 실행됩니다. 작업이 완료된 후 메인 스레드로 돌아와 프로그램이 종료됩니다.

요약

  1. suspend 함수는 코루틴에서 비동기 작업을 정의하는 함수입니다. 이 함수는 중단될 수 있으며, 코루틴 내에서만 호출할 수 있습니다.
  2. withContext는 코루틴의 컨텍스트를 다른 디스패처로 변경하여, 입출력 작업이나 CPU 집약적인 작업을 별도의 스레드에서 처리하고, 메인 스레드의 부하를 줄일 수 있습니다.
  3. suspend 함수와 withContext의 결합은 복잡한 비동기 작업을 동기적 흐름처럼 간결하게 처리할 수 있게 해주며, 스레드 자원의 효율적인 사용을 가능하게 합니다.
728x90
댓글