개요
해당 개시글은 Welcome to Kotlin hands-on (kotlinlang.org)를 번역한 게시글입니다.
Using Suspend Function
Retrofit은 코루틴을 지원하고 이를 활용할 것이다. Call<List<Repo>>를 반환하는 대신 API 호출을 suspend 함수로 정의한다.
interface GitHubService {
@GET("orgs/{org}/repos?per_page=100")
suspend fun getOrgRepos(
@Path("org") org: String
): List<Repo>
}
이면에 있는 메인 아이디어는 요청을 수행하는데 suspend function을 사용한다는 것이고 스레드가 차단되지 않는다는 것이다. 이게 어떻게 동작하는지는 나중에 논의할 것이다.
지금은 getOrgRepos가 Call 객체를 반환하는 대신 직접적인 결과를 반환한다는 것에 주목해라. 만약 결과가 성공적이지 않다면 예외를 던질 것이다.
대안으로 Retrofit은 결과를 Response로 래핑하는 것을 지원한다. 이 경우에서 결과 body가 제공되며 수동으로 오류를 확인할 수 있다. 이 튜토리얼에서는 Response로 래핑하는 방법을 사용할 것이다.
interface GitHubService {
// getOrgReposCall & getRepoContributorsCall declarations
@GET("orgs/{org}/repos?per_page=100")
suspend fun getOrgRepos(
@Path("org") org: String
): Response<List<Repo>>
@GET("repos/{owner}/{repo}/contributors?per_page=100")
suspend fun getRepoContributors(
@Path("owner") owner: String,
@Path("repo") repo: String
): Response<List<User>>
}
해야 될 작업은 suspend function을 사용하기 위해 contributor를 로딩하는 함수의 코드를 변경하는 것이다.
Suspend function은 어디에서나 호출할 수 있는 것은 아니다. 만약 loadContributorsBlocking에서 호출하다면 "Suspend function 'getOrgRepos' should only be called from a coroutine or another suspend function"라는 에러를 보게될 것이다. 따라서 새 API를 활용하려면 loadContributors를 suspend로 표시해야 한다.
Solution
loadContributorsBlocking에서 .getOrgReposCall(req.org).execute()을.getOrgRepos(req.org)로 변경하기만 하고 이를 contributor를 로딩하는 곳에서도 똑같이 처리한 뒤 suspend 키워드만 붙이면 된다.
suspend fun loadContributorsSuspend(service: GitHubService, req: RequestData): List<User> {
val repos = service
.getOrgRepos(req.org)
.also { logRepos(req, it) }
.bodyList()
return repos.flatMap { repo ->
service.getRepoContributors(req.org, repo.name)
.also { logUsers(repo, it) }
.bodyList()
}.aggregate()
}
loadContributorsSuspend는 suspend 함수로 정의되어야 한다는 점을 잊지 마라.
이제 API가 Response를 직접 반환하기 때문에 Response를 반환하는 execute()를 호출할 필요가 없다. 그러나 이것은 Retrofit 라이브러리에 처리하는 사항이다. 다른 라이브러리에서는 API가 다르지만 개념 자체는 동일하다.
suspend 함수를 사용한 코드는 "blocking" 버전과 놀랍게도 비슷하다. 읽기도 좋고 무엇을 하려하는지 정확히 표현한다.
"blocking" 버전과의 큰 차이점은 스레드를 차단하지 않고 coroutine을 일시 중지한다는 점이다.
block -> suspend
thread -> coroutine
코투린은 경량 스레드라고 자주 불린다. 이 의미는 스레드에서 코드를 실행하는 것과 유사하게 코루틴에서 코드를 실행할 수 있음을 의미한다. 이전의 차단하는 작업이 이제 코루틴을 일시 중단 하도록 한다.
어떻게 새로운 코루틴을 시작할 수 있을까? 만약 loadContributorsSuspend을 어떻게 호출하는지 방법을 살펴보면 launch 내부에서 호출하는 것을 볼 수 있다. launch는 람다를 인수로 취하는 라이브러리 함수다.
launch {
val users = loadContributorsSuspend(req)
updateResults(users, startTime)
}
launch는 새로운 연산을 시작한다. 이 연산은 데이터를 로딩하고 결과를 보여주는 역할을 담당한다. 이 연산은 일시 중지가능하다: 네트워크 요청을 수행하는 동안 'suspend' 될수 있고 스레드를 놓을 수 있다. 해당 네트워크 요청이 결과를 반환 햇을 때 해당 연산은 다시 재개(resume)된다.
이렇게 일시 중단 가능한 연산을 코루틴이라 부르고 이 경우에서는 간단하게 launch를 통해 데이터 로딩 및 결과를 보여주는 새로운 코루틴을 생성한다.
코루틴은 스레드 위에서 실행되며 일시 중단될 수 있는 연산이다. 코루틴이 "suspend"되면 해당 연산이 일시 중지되고 스레드에서 제거되어 메모리에 저장된다. 한편 스레드는 이어서 다른 활동을 자유롭게 수행할 수 있다.
해당 연산이 계속될 준비가 마쳐지면 다시 스레드로 돌아오게 된다.
다시 loadContributorsSuspend 예제로 돌아오자. 각 contributors 요청은 이제 일시 중지 메커니즘을 통해 결과를 기다린다. 첫번째로 새로운 요청이 보내진다. 그런 다음 해당 응답을 기다리는 동안 모든 "load contributors" 코루틴이 일시 중지된다. (이 버전은 아직 동시성을 제공하진 않는다.) 해당 코루틴은 상응하는 응답을 받은 후에 다시 재개(resume)된다.
해당 응답이 오기를 기다리는 동안 스레드는 자유롭게 다른 작업을 수행할 수 있다. 이게 바로 사용자가 Coroutine을 통해 로드 될 때 모든 요청이 UI 스레드에서 요청되더라도 UI가 반응할 수 있는 이유이다. 아래 로그가 모든 요청이 UI 스레드에서 수행된 것을 확신시킨다.
2538 [AWT-EventQueue-0 @coroutine#1] INFO Contributors - kotlin: loaded 30 repos
2729 [AWT-EventQueue-0 @coroutine#1] INFO Contributors - ts2kt: loaded 11 contributors
3029 [AWT-EventQueue-0 @coroutine#1] INFO Contributors - kotlin-koans: loaded 45 contributors
...
11252 [AWT-EventQueue-0 @coroutine#1] INFO Contributors - kotlin-coroutines-workshop: loaded 1 contributors
또한 해당 로그는 해당 코드가 실행되는 코루틴을 확인할 수도 있다.
이 버전에서는 결과를 기다리는 동안 다른 요청을 보내기 위해 스레드를 재사용하지 않았다. 코드를 순차적으로 작성했기 때문이다. 새로운 요청은 이전 결과가 수신된 경우에만 전송된다. suspend 함수는 스레드를 공정하게 처리하고 대기를 위해 차단되진 않지만 아직 동시성을 통해 처리한 것은 아니다. 이를 어떻게 개선할 수 있는지 다음 포스팅을 통해 보자.
'안드로이드 > Kotlin' 카테고리의 다른 글
[Kotlin] Introduction to Coroutines and Channels-4 (0) | 2021.06.28 |
---|---|
[Kotlin] Introduction to Coroutines and Channels-3 (0) | 2021.06.28 |
[Kotlin] Introduction to Coroutines and Channel-1 (0) | 2021.06.26 |
[Kotlin] 코틀린 공식 문서 - Cancellation and Timeout (0) | 2021.06.26 |
[Kotlin] 코틀린 공식 문서 - Coroutines basics (0) | 2021.06.26 |