📱 안드로이드 Android ~ Kotlin

[Android/Kotlin] 코루틴 coroutine

핑크빛연어 2021. 9. 21. 17:56

 

코루틴 Coroutine

 

Android의 비동기 프로그래밍에 권장되는 솔루션으로, 비동기적으로 실행되는 코드를 간소화하기 위해 Android 에서 사용할 수 있는 동시 실행 설계 패턴.

안드로이드의 Background Thread 에서 코드를 처리할 때 사용하는 방법이다.

Co(협력, 같이, 함께, 동시에) 라는 뜻과 Routine(특정한 일을 실행하기 위한 일련의 명령) 이라는 두 단어의 합성어로, 동시성 프로그래밍의 개념을 코틀린에 도입한 것

다양한 언어에서 이미 지원하고 있는 개념이다.

통신 시 비동기 처리하는 방법으로 오랜 기간 사용되었던 AsyncTask 가 Deprecated 되면서, 메모리를 효율적으로 사용하면서 AsyncTask 를 대체하는 비동기 처리 방법으로 사용되고 있다.

 

 

🚨 Coroutines - Thread 차이

프로세스 (Process)

프로세스(Process) 란 메모리에 올라와 실행되는 프로그램의 인스턴스로, 1개의 앱에서는 1개 이상의 프로세스(Process) 가 실행된다. 1개의 프로세스(Process) 는 1개의 힙(Heap) 메모리가 할당된다. 프로세스(Process) 에는 앱을 실행시키기 위한 1개의 흐름인 쓰레드(Thread) 가 실행된다. 프로세스(Process) 는 1개 이상의 스레드(Thread) 가 필요하다. 스레드(Thread) 는 각각의 스택(Stack) 을 통해 관리된다.

 

코루틴 (Coroutines)

쓰레드 Thread 코루틴 Coroutines
- Task 단위 : Thread
- 각 작업에 Thread 를 할당
- 각 Thread 는 자체 Stack 메모리를 가지며, JVM Stack 영역 차지
- Task 단위 : Object(Coroutine)
- 각 작업에 Obejct(Coroutine) 을 할당
- Coroutine 은 객체를 담는 JVM Heap 에 적재

 

코루틴(Coroutine) 이 하나의 실행-종료되어야 하는 일(Job) 이라고 한다면, 쓰레드(Thread) 는 그 일이 실행되는 곳이다.
따라서 하나의 쓰레드(Thread) 에 여러 개의 코루틴(Coroutine) 이 동시에 실행될 수 있다.
하나의 쓰레드(Thread) 가 끝날 때까지 계속되는 것과는 달리 코루틴(Coroutine) 은 실행 중간에 다른 작업을 하러 갔다가 돌아와서 작업을 다시 할 수 있다. 

 

 

🚨 장점 및 사용

 

  • 경량: 코루틴(Coroutine) 을 실행 중인 스레드를 차단하지 않는 정지를 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있다. 정지는 많은 동시 작업을 지원하면서도 차단보다 메모리를 절약한다.
  • 메모리 누수 감소구조화된 동시 실행을 사용하여 범위 내에서 작업을 실행한다.
  • 기본으로 제공되는 취소 지원: 실행 중인 코루틴 계층 구조를 통해 자동으로 취소가 전달된다.
  • Jetpack 통합: 많은 Jetpack 라이브러리에 코루틴을 완전히 지원하는 확장 프로그램이 포함되어 있다. 일부 라이브러리는 구조화된 동시 실행에 사용할 수 있는 자체 코루틴 범위도 제공한다.

- 기본 스레드를 차단하여 앱이 응답하지 않게 만들 수도 있는 장기 실행 작업을 관리하는 데 도움이 된다. 더 명확하고 간결한 앱 코드를 작성하는 방법으로 사용된다.

- network 통신 (Retrofit, Volley 등), 내부 저장소 접근(Room 등) 에서 대표적으로 사용된다

- 코루틴(Coroutine) 은 코드가 아주 간단하고, 블록으로 처리를 할 수 있기 때문에 하나의 Request-Response 송수신 후에 또 다른 연속적인(sequential) 작업을 하기 좋게 최적화 되어 있는 것 같다.

 

Context로 Scope 를 만들고, Builder 를 이용하여 그 Scope 안에서 실행한다.
1. CoroutineContext(Dispatchers.IO, Dispatchers.Main ..)를 이용하여 코루틴(Coroutine) 이 실행될 CoroutineScope 를 만들고
2. 만들어준 CoroutineScope 에서 CoroutineBuilder(launch, async)를 이용하여 { } 안의 코드를 코루틴(Coroutine) 으로 실행시킨다.

 

 

💡 의존성 추가

build.gradle(:app)

하단 dependencies {} 안에 coroutine 에 대한 라이브러리를 사용하기 위해 추가하여 사용한다.

plugins { ... }
android { ... }

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

 

 

💡 Coroutine Context

 

코루틴(Coroutine) 을 적당한 쓰레드(Thread) 에 할당하여 코루틴(Coroutine) 이 어디서 실행될지를 정해주는 역할.
CoroutineContext 는 코루틴(Coroutine) 이 실행중인 여러 작업(Job) 과 Dispatchers 를 저장하는 일종의 맵.
코루틴 컨텍스트 CoroutineContext 에는 Main, IO, Default 의 3가지가 있다.

Dispatchers.Main : 메인 쓰레드에 대한 Context. UI를 구성하는 작업이 모여있는 Thread Pool.  UI 갱신이나 Toast 등의 View 작업에 사용.

Dispatchers.IO : (파일 혹은 소켓을) 읽고 쓰는 작업이 모여있는 Thread Pool. 네트워킹이나 내부 DB 접근 등 백그라운드에서 필요한 작업을 수행할 때 사용. 

Dispatchers.Default : 기본 Thread Pool. 크기가 큰 리스트를 다루거나 필터링을 수행하는 등 무거운 연산이 필요한 작업에 사용.

CoroutineScope(Main).launch {
    // do something
}

CoroutineScope(IO).launch {
    // do something
}

CoroutineScope(Default).launch {
    // do something
}

 

 

💡 Coroutine Scope

 

Coroutine Scope 는 새로운 코루틴(Coroutine) 을 생성함과 동시에 실행되어야 할 작업(Job) 의 범위를 지정한다. 
코루틴(Coroutine) 을 제어(작업 취소, 작업이 끝날 때 까지 대기하는 것) 할 수 있는 범위(Scope)를 지정해주는 역할을 한다.
코루틴(Coroutine) 은 항상 자신이 속한 Scope 를 참조해야한다. 이후에 cancel 로 모두 취소도 가능하다.
그래서 하나의 작업이 끝나고 다른 작업을 호출하다가 실패하게 된다면 전체가 취소 처리 된다.

 

GlobalScope : 

 앱이 실행될 때부터 앱이 종료될 때까지 코루틴(Coroutine) 을 실행시킬 수 있는 Scope.
앱의 생명주기와 함께 동작하기 때문에 실행 도중에 별도 생명주기 관리가 필요없다. 
시작-종료까지 긴 기간 실행되는 코루틴(Coroutine) 의 경우에 적합.
어떤 Activity 에서 GlobalScope 를 통해 실행된 코루틴(Coroutine) 은 Activity 가 종료되어도 해당 코루틴(Coroutine) 이 완료될 때까지 동작한다.

 

coroutineScope

가장 기본이 되는 방식.
특정 코루틴(Coroutine) 이 필요할 때만 새로 선언해주고, 완료되거나 필요 없어지면 종료되도록 하는 Scope.
버튼을 눌러 다운로드 하거나 서버에서 이미지를 열때 등 필요할 때만 열고, 완료되면 닫아줄 때 사용할 수 있다. 
GlobalScope 와 다르게 Dispatchers 를 지정할 수 있는데 이는 코루틴(Coroutine) 이 실행될 쓰레드(Thread) 를 지정하는것 이다.

 

ViewModelScope

jetpack 아키텍처의 ViewModel 컴포넌트 사용시 ViewModel 인스턴스에서 사용하기 위해 제공되는 Scope.
ViewModelScope로 실행되는 코루틴(Coroutine) 은 ViewModel 인스턴스가 소멸될 때 자동으로 취소된다.

 

 

💡 Coroutine Builder

 

CoroutineScope 의 확장함수로, CoroutineBuilder 의 종류로는 launch{}, async{} 가 있다.
코루틴(Coroutine) 을 launch{}, async{} 로 시작할 수 있다.

launch : Coroutine 상태 관리(제어) (return 값이 없는 Job 객체를 반환)

async : Coroutine 상태 관리(제어) + 결과 까지 반환 받을 수 있다 (return 값이 있는 Deffered 객체(마지막 값 Return)를 반환)

withContext : 부모 코루틴에 의해 사용되던 컨텍스트와 다른 컨텍스트에서 코루틴을 실행시킬 수 있다. 코루틴에서 결과를 반환할 때 async 대신 유용하게 쓸 수 있다.

val job = scope.launch {  //Job
  //launch TODO
}

val deffered = scope.async {  //Deffered
  //async TODO
}

 

 

💡 Coroutine 상태관리

 

코루틴(Coroutine) 을 생성하고 상태 관리 메서드를 호출해서 중단, 지연 할 수 있다.

 

cancel() : 

코루틴(Coroutine) 의 동작을 멈추는 상태관리 메소드. 
스코프 안에 여러 코루틴이 존재하는 경우 하위 코루틴을 모두 멈춘다.
아래 코드에서 job 을 cancel() 하면 안에 있던 job01 도 중단된다.

val job = CoroutineScope(Dispatchers.Default).launch {
        val job01 = launch {
            for (i in 0..10) {
                delay(500)
                println("$i")
            }
        }
    }

    btn.setOnClickListener {
        job.cancel()
    }

 

join()

코루틴 내부에 여러 launch 블럭이 있는 경우 모두 새로운 코루틴으로 분기되어 동시 실행되기 때문에 순서를 정할 수 없다. 그러나 순서를 정해야 한다면 join() 을 사용해 순차적으로 실행되도록 할 수 있다.

CoroutineScope(Dispatchers.Default).launch {
  launch {
	for (i in 0..5) {
		delay(500)
		println("$i")
	}
  }.join()

  launch {
	for (i in 6..10) {
		delay(500)
		println("$i")
	}
  }
}

 

delay()

코루틴의 실행을 지연할 수 있다.

 

 

💡 Coroutine suspend

 

코루틴(Coroutine) 안에서 일반적인 메소드는 호출할 수 없다. 코루틴(Coroutine)의 코드는 잠시 실행을 멈추거나(suspend) 다시 실행될(resume) 수 있기 때문이다. 코루틴(Coroutine) 에서 실행할 수 있는 메소드를 만드려면 메소드를 정의할 때 suspend 를 붙여주면 된다. suspend 메소드는 안에서 다른 코루틴(Coroutine) 을 실행할 수도 있다.

메소드가 비동기 환경(Asynchronous)에서 사용될 수 있다는 의미를 내포하고, 비동기 함수인 suspend 메소드는 다른 suspend 메소드, 혹은 코루틴(Coroutine) 내에서만 호출할 수 있고, 아닌 곳에서 그냥 호출하려고 하면 warning이 뜨면서 Suspend function (FUNCTION_NAME) should be called only from a coroutine or another suspend function 이런 메세지가 나온다.

코루틴(Coroutine)의 Context 를 사용해 메소드를 실행하려면 suspend 를 붙여주어야 한다. suspend를 붙인 메소드는 Coroutine Scope 안에서 실행이 가능하다.

 

 

💡 withTimeoutOrNull

 

네트워크 타임아웃을 처리할 때 withTimeoutOrNull(timeMillis) 를 이용하면 손쉽게 처리할 수 있다.
timeMillis 를 초과하는 경우 null 을 반환한다.

CoroutineScope(Dispatchers.IO).launch {
    val resultStr = withTimeoutOrNull(10000) {
        getResultFromNetwork()
    }

    if (resultStr != null) {
        withContext(Main) {
            textView.text = resultStr
        }
    }
}

 

 

 

https://developer.android.com/kotlin/coroutines?hl=ko 

 

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

Android의 Kotlin 코루틴 코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확

developer.android.com

 

 

 

 

 

 

728x90
반응형