코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다.
코루틴은 비동기 코드 작업을 수행하기 위한 개념이다.
비동기 작업이란? 함수가 특정 시점에서 실행을 일시 중단하고, 나중에 다시 시작할 수 있다는 개념
코루틴은 Thread처럼 parallel하게 동작할 수있고, 기다리고, 통신할 수 있습니다. 하지만 Thread와 가장큰 차이는 그 사용비용이 매우 저렴하다.
1. 가볍다
2. 메모리 누수 감소 구조화된 동시성을 가진다.
3. Jetpack 통합 -> jetpack 라이브러리에 코루틴을 완전히 지원하는 [확장 프로그램](https://developer.android.com/topic/libraries/architecture/coroutines?hl=ko)이 포함되어 있다.
운영체제에서는 Context Switching 기법을 이용해 스레드를 교체한다(각 스레드마다 time sequence를 가지고 Concurrency하게 돌아간다.)
근데 이 Context Switching은 생각보다 비용이 많이 드는 작업이다.
따라서 Single Thread로 동시성을 제공하는 코루틴은 이 부분에서 성능적으로 이득이 될 가능성이 높다.
Thread간의 작업 교환은 System Call 또는 Blocking Call을 사용하는데, 이것 또한 코루틴의 작업 교환보다 비용이 많이 든다.
여러 Thread의 동기화를 위한 Mutex, Semaphore 기법 등을 코루틴에서는 사용할 필요가 없다.
Coroutine 은 Thread 의 대안이 아니라, Thread 를 더 잘게 쪼개어 사용하기 위한 개념이다.
한 쓰레드에서 다수의 Coroutine 을 수행할 수 있고, Context Switching 이 필요없다.
작업의 단위를 Coroutine Object 로 축소하면서 하나의 Thread 가 다수의 코루틴을 다룰 수 있기 때문에, 작업 하나하나에 Thread 를 할당하며 메모리 낭비, Context Switching 비용 낭비를 할 필요가 없음
같은 프로세스 내의 Heap 에 대한 Locking 걱정 또한 사라짐으로써 Semaphore, Mutex와 같은 기법도 필요가 없다.
RoomDB에서 데이터를 Insert 하는 과정을 Coroutine으로 진행해 보고자 한다.
MemoViewModel에서 insert()함수를 호출하여 DB에 메모를 추가하려고 한다.
memoViewModel.insert(memo)
MemoViewModel에서는 해당 함수를 호출받아 해당 viewModelScope내에서 insert()함수를 실행한다.
// in MemoViewModel
fun insert(memo: Memo) {
viewModelScope.launch(Dispatchers.IO) {
// this: CoroutineScopre
// MemoViewModel의 LifeCycle이 종료되면 이 코루틴도 같이 종료된다.
repository.insert(memo)
}
}
이 코루틴은 viewModelScope로 시작되므로 ViewModel 범위에서 실행된다.
사용자가 화면 밖으로 이동하는 것으로 인해 ViewModel이 소멸되는 경우 viewModelScope가 자동으로 취소되고 실행 중인 모든 코루틴도 취소된다.
이는 jetpack 라이브러리에 코루틴을 완전히 지원하는 확장 프로그램중 하나이다.
// In Repository
// 만약 ViewModel이 액티비티의 context를 쓰게 되면, 액티비티가 destroy 된 경우에는 메모리 릭이 발생할 수 있다.
// 따라서 Application Context를 사용하기 위해 Applicaion을 인자로 받는다.
class MemoRepository(application: Application) : ViewModel() {
private val memoDatabase = MeMoDatabase.getInstance(application)!!
private val memoDao: MemoDao = memoDatabase.memoDao()
private val memos: LiveData<List<Memo>> = memoDao.getAll()
private var filtermemos : LiveData<List<Memo>> = memoDao.getFilterd("")
fun getAll(): LiveData<List<Memo>> {
return memos
}
// 쓰레드를 사용한 DB접근
fun getFilterMemo(findstr:String): LiveData<List<Memo>> {
// ViewModel에서 DB에 접근을 요청할 때 수행할 함수를 만들어둔다.주의할 점은 Room DB를 메인 스레드에서 접근하려 하면 크래쉬가 발생한다
try {
val thread = Thread(Runnable {
filtermemos = memoDao.getFilterd(findstr)
})
thread.start()
} catch (e: Exception) { }
return filtermemos
}
// 코루틴을 사용한 DB접근
fun insert(memo: Memo) {
viewModelScope.launch(Dispatchers.IO) {
try{
memoDao.insert(memo)
} catch (e:java.lang.Exception){
// Repository 예외처리
}
}
}
}
Android Developer의 내용을 발췌
Kotlin 코루틴을 사용하면 네트워크 호출이나 디스크 작업과 같은 장기 실행 작업을 관리하면서 앱의 응답성을 유지하는 깔끔하고 간소화된 비동기 코드를 작성할 수 있다.
Dispatcher는 코루틴을 특정 스레드에서 실행할 수 있도록 도와주는 기능이다.
코루틴에서는 디스패처를 이용하여 다양하게 스코프를 지정할 수 있다.
Dispatchers.Main - 이 디스패처를 사용하여 기본 Android 스레드에서 코루틴을 실행합니다. 이 디스패처는 UI와 상호작용하고 빠른 작업을 실행하기 위해서만 사용해야 합니다.
Android UI 프레임워크 작업을 실행하거나, LiveData 객체를 업데이트하는 작업을 수행
Dispatchers.IO - 이 디스패처는 기본 스레드 외부에서 디스크 또는 네트워크 I/O를 실행하도록 최적화되어 있다.
네트워크 입출력 등의 대기시간이 있는 작업에 적합하고, 파일을 읽거나 쓰는 작업에 용이
Dispatchers.Default - 이 디스패처는 CPU를 많이 사용하는 작업을 기본 스레드 외부에서 실행하도록 최적화되어 있다.
launch는 새 코루틴을 시작하고 호출자에게 결과를 반환하지 않습니다. '실행 후 삭제'로 간주되는 모든 작업은 launch를 사용하여 시작할 수 있습니다.
async는 새 코루틴을 시작하고 await라는 정지 함수로 결과를 반환하도록 허용합니다.
// launch는 결과를 반환하지 않고 수행시 job이 반환된다.
CoroutineScope(Dispatchers.Main).launch{
//TODO .......
val job: Job = launch{println(1)}
}
// async는 결과를 반환하며 Deferred로 감싸서 반환한다.
// Deferred는 미래에 올 수 있는 값을 담아놓을 수 있는 객체
suspend fun fetchTwoDocs() =
coroutineScope {
val deferredOne : Deferred<Int> = async { fetchDoc(1) }
val deferredTwo : Deferred<Int> = async { fetchDoc(2) }
deferredOne.await()
deferredTwo.await()
}
await()메소드는 결과가 반환될때까지 기다린다.
이때동안 코루틴은 일시중단되며 await()메소드는 일시중단 가능한 코루틴내부에서만 사용가능 그래서 함수앞에 Suspend를 붙여 일시중단이 가능한 함수로 선언한다.