코루틴은 비동기 프로그래밍을 위한 개념입니다. 코루틴은 스레드와 유사한 동작 방식을 가지며, 스레보다 가볍고 효율적인 비동기 작업 처리를 가능하게 해줍니다. 또한 비동기 작업을 필요한 시점에서 일시 중단하거나 재개할 수 있도록 해줍니다.
주로 비동기 작업이 필요한 네트워크 요청, 파일 입출력, 데이터베이스 액세스 등에서 사용하며, UI 스레드를 차단하지 않고 작업을 수행할 수 있습니다.
관련 함수
launch
: launch 함수는 가장 간단한 형태의 코루틴 빌더입니다. 비동기 작업을 코루틴으로 실행할 때 사용되며, 반환값이 없습니다.suspend
: suspend 키워드는 함수를 코루틴으로 사용할 수 있도록 만들어줍니다. suspend 함수는 일시 중단 가능하며, 다른 코루틴에서 호출될 수 있습니다.withContext
: withContext 함수는 코루틴 컨텍스트를 변경하고, 지정된 블록 내에서 함수를 실행합니다. 다른 스레드에서 실행하거나 코루틴 컨텍스트를 변경할 때 사용됩니다.CoroutineScope
: CoroutineScope 인터페이스는 코루틴 범위를 정의하고 관리하는 데 사용됩니다. 코루틴 범위 내에서 launch나 async 함수를 호출할 수 있습니다.
백그라운 스레드에서 실행하는 예시를 보겠습니다.
우선 다음과 같이 동기식으로 LoginRepository.kt 와 LoginViewModel.kt 가 구현되었을 때의 문제점을 확인해 보겠습니다.
LoginReository.kt
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// Function that makes the network request, blocking the current thread
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
makeLoginRequest가 동기식이며, 호출하는 스레드를 차단합니다.
LoginViewModel.kt
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
LoginViewModel은 네트워크 요청을 보낼 때 UI스레드를 차단합니다.
이를 코루틴을 사용하여 기본 스레드 외부로 이동시켜 I/O 스레드에서 네트워크 요청을 하게 할 수 있습니다. 이를 위해서 아래와 같이 수정할 수 있습니다.
우선 build.gradle 파일에 다음 종속 항목을 추가합니다.
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
viewModelScope는 KTX 확장 프로그램에 포함된 사전 정의된 CoroutineScope 입니다.
launch는 코루틴을 만들고 함수 본문의 실행을 해당하는 디스패처에 전달하는 함수입니다.
Dispatchers.IO 는 코루틴을 I/O 작엉용으로 예약된 스레드에서 실행해야 함을 나타냅니다.
하지만 위와 같이 구현을 하면 makeLoginRequest 를 호출하는 모든 항목이 명시적으로 실행을 기본 스레드 외부로 이동해야 한다는 문제점이 있습니다. 이를 위해 Respository를 다음과 같이 수정합니다.
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO) 를 사용해서 코루틴 실행을 I/O 스레드로 이동하여 호출 함수를 기본 안전 함수로 만들고 필요에 따라 UI를 업데이트 합니다.
suspend 는 코루틴 내에서 함수가 호출되도록 하는 kotlin의 방법 입니다.
이렇게 되면 makeLoginRequest 가 실행을 기본 스레드 외부로 이동하게 되므로, login 함수의 코루틴이 기본 스레드에서 실행될 수 있습니다. 아래는 이를 반영하여LoginViewModel을 수정한 코드 입니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun makeLoginRequest(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
launch가 Dispatchers.IO 매개변수를 사용하지 않습니다. Dispatcher를 launch에 전달하지 않으면 viewModelScope에서 실행된 코루틴은 기본 스레드에서 실행됩니다.
login 함수의 동작은 다음과 같이 실행됩니다.
View
레이어에서 login()
함수를 호출합니다.launch
가 기본 스레드에 새 코루틴을 만들고 코루틴이 실행을 시작합니다.loginRepository.makeLoginRequest()
호출은 makeLoginRequest()
의 withContext
블록 실행이 끝날 때까지 코루틴의 추가 실행을 정지합니다.withContext
블록이 완료되면 login()
의 코루틴이 네트워크 요청의 결과와 함께 기본 스레드에서 실행을 재개합니다.