https://www.notion.so/5a8986a978f049338602585dec3b5e8b
코루틴/어댑터
목차
www.notion.so
https://www.notion.so/fd951ba927db46d993bc88aaf454c53f
코루틴 추가
코루틴의 개념
www.notion.so
내 노션에서 정리한 것을 바탕으로 블로그에 깔끔하게 정리해보고자 한다
이론의 대부분은 아래 영상을 참고했음
https://www.youtube.com/watch?v=xSgZS9e3qCU
Coroutine
비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴
코루틴은 코루틴이 시작된 스레드를 중단하지 않으면서 비동기적으로 실행되는 코드
기본 스레드를 차단하여 앱이 응답하지 않게 만들수도 있는 장기 실행 작업을 관리하는데 도움
기존의 AsyncTask 또는 다수 스레드 관리를 직접 해주지 않아도 됨
AsyncTask
1) 손쉬운 비동기 프로그래밍
2) 하지만 메모리 누수 등 여러 문제 존재
3) deprecated@API 30
4) 따라서 대체재로 코루틴을 권장
코루틴과 스레드
1) 메모리 구조의 차이
프로그램이 휴대폰의 메모리에 로드되고, 로드된 인스턴스트를 프로세스라 함
실행된 프로세스는 그 안에서 여러개의 독립된 스레드를 갖게 됨
프로세스는 자기가 사용하기 위한 메모리를 할당받는데 이것을 heap 메모리
프로세스 안에서 돌아가는 각각의 스레드에도 메모리가 할당되는데 이것이 stack 메모리
스레드는 stack이라는 독립된 메모리 영역을 할당받음
코루틴은 stack을 할당받지 않고 프로세스에 할당된 heap 메모리 영역을 공유해서 사용
따라서 비동기 작업을 수행한다는 점에선 코루틴과 스레드는 비슷하지만
메모리 측면에서 보면 코루틴은 함수에 더 가까운 구조!
2) 수행 방식의 차이
- 코루틴은 비선점형 멀티태스킹
스레드는 선점형 멀티태스킹 - 스레드는 실제로 멀티코어를 사용해서 동일한 시간에 두가지 작업을 할 수 있음 → 병행성
- 코루틴은 두개의 코루틴이 있다고 해도 두 코루틴이 동시에 실행되지 않음 → 동시성은 있지만 병행성은 없음
3) 코루틴의 장점
- 3개의 스레드를 사용해야 하는 작업을 코루틴 3개로 대체해서 사용한다면?
스레드마다 할당해줘야했던 스택 메모리를 할당해줄 필요가 없어짐
→ 메모리 줄어듦 - 여러개의 스레드를 사용하다보면 스레드 간의 전환을 위해 context switching 발생
코루틴에선 프로세스가 가진 힙 메모리 안에서 코루틴이 돌아가기 때문에 context switching 자체가 필요없음
→ context switching에 필요한 오버헤드 줄어듦 - 한 개의 스레드 안에 여러개의 코루틴을 돌릴 수 있기 때문에 스레드를 불필요하게 많이 만들 필요도 없어짐
4) 코틀린에서의 사용
suspend 키워드 사용
일반적인 함수는 처음부터 끝까지 실행하지만 suspend 함수는 일시정지가 가능하며
다른 작업 후 다시 돌아와서 멈춘 지점부터 다시 실행이 가능함
코루틴 구조
1) CoroutineScope
코루틴이 어떤 범위에서 동작하는지 정의
- CoroutineScope
코루틴은 스코프 안에서만 동작함
특히 일시중단 함수(join, await, join, suspend)는 코루틴 스코프 안에서만 호출 가능 - GlobalScope
애플리케이션이 시작하고 종료될때까지 계속 유지되기 때문에 별도 생명주기 관리가 필요 없음(Singleton)
하지만 효율적인 어플을 만들기 위해선 필요할때 코루틴을 생성하고, 사용하지 않을 때 정리해줘야하기 때문에
GlobalScope는 잘 사용하지 않음
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
2) CoroutineContext
어떤 스레드에서 코루틴을 돌릴 지 정의
Context는 Dispatchers와 Job으로 구성
Dispatchers
코루틴이 실행되는 스레드를 지정
a) Default : CPU연산을 많이 사용하는 작업에서 사용
b) IO : File I/O, Network I/O
c) Main : UI 스레드에서 UI관련 작업에서 사용
d) Unconfined : 일반적인 용도로는 사용하지 않음
Job & Deferred
코루틴을 Job이라는 object로 만들어 취소, 예외처리를 함으로써 코루틴 흐름 제어 용이하게 할 수 있음
* Deferred : 결과 값이 없는 Job을 확장하는 인터페이스로 결과값을 가지는 Job임
val job = scope.launch {
// New Coroutine
}
job.cancle() // 취소
a) state
코루틴은 일시정지 시킬 수 있는 작업의 흐름이기 때문에
Job은 코루틴의 여러가지 상태를 반영할 수 있도록 여러 상태를 가질 수 있도록 설계되어있음
각 state마다 isActive, isCompleted, isCancelled 3개의 플래그가 있어서
Job이 현재 어떤 state인지 플래그를 통해 알 수 있음
b) Job states cycle
Job이 처음 생성되면 New 상태, 시작하면 Active 상태, 작업을 하면 Completing 상태, 작업이 완전히 끝나면 Completed 상태, 작업이 도중에 취소되거나 실패하면 Cancelling 상태, cancel이 완전히 끝나면 Cancelled 상태로 들어가게 됨
c) methods
- cancel : 작업중인 코루틴을 Cancelling 상태로 변경
- join : cancel된 작업을 병렬처리하지 않고 완전히 끝날때까지 기다리게 함
- start : 새로 만들어진 객체 시작
3) CoroutineBuilder
작업을 수행하고 값 리턴 여부 등에 대해 정의
a) launch : job 객체 반환
b) async : Deferred 객체 반환
cf) launch
launch는 코루틴 작업이 실행됐을 때 그냥 작업을 수행하고 끝이지만
async는 작업을 수행하고 남은 값은 반환됨. 이 값은 Deferred 형태의 object로 반환됨
c) runBlocking : 메인스레드를 블로킹하기 때문에 테스트 용도로만 사용, 코루틴 용도론 사용 x
d) withContext : Dispatcher switch
withContext를 사용해서 Dispatcher를 switch하면 안드로이드 OS에서
스위칭을 관리하기 때문에 오버헤드를 훨씬 적게 관리할 수 있음
코루틴 지연
1) delay
delay(1000) // 1초 지연
thread의 sleep과 유사하지만 sleep은 스레드를 아예 멈추는데 비해
delay는 코루틴이 멈추는게 아니고 숫자를 세면서 대기함
2) join
launch로 실행한 job에 대해선 join으로 실행이 끝날때까지 대기시킬 수 있음
3) await
async로 실행한 job에 대해서 await로 대기시킬 수 있음
async로 실행한 job은 Deferred를 반환하기 때문에 await 함수를 통해 값을 반환할 수 있는 것
코루틴 취소
1) cancel
Cancelling state로 변환
2) cancelAndJoin
cancel 하고 cancel 상태가 끝날 때 까지 기다려 주는 것
3) withTimeout
제한 시간을 설정하고 작업이 제한 시간보다 오래 걸리면 취소를 하고 예외를 던져주는 역할
4) withTimeoutOrNull
어떤 작업을 제한 시간 내에 하지 못했을 경우 Null을 던지도록 하는 역할
코루틴 실습
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}
2) suspend 사용하지 않은 경우
package com.example.coroutinestest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.coroutinestest.databinding.ActivityMainBinding
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
main()
}
fun main() {
CoroutineScope(Dispatchers.IO).launch {
// 병렬로 2개 함수 실행
async { nonSuspendTask1() }
async { nonSuspendTask2() }
}
}
fun nonSuspendTask1() {
Thread.sleep(3000)
Log.d("TAG", "[nonSuspendTask1] After 3s in (${Thread.currentThread().name})")
Thread.sleep(3000)
Log.d("TAG", "[nonSuspendTask1] After 6s in (${Thread.currentThread().name})")
Log.d("TAG", "[nonSuspendTask1] END in (${Thread.currentThread().name}) *****")
}
fun nonSuspendTask2() {
Thread.sleep(1000)
Log.d("TAG", "[nonSuspendTask2] After 1s in (${Thread.currentThread().name})")
Thread.sleep(3000)
Log.d("TAG", "[nonSuspendTask2] After 4s in (${Thread.currentThread().name})")
Log.d("TAG", "[nonSuspendTask2] END in (${Thread.currentThread().name})*****")
}
}

다른 작업을 수행할 수 없기 때문에 각 함수를 서로 다른 스레드에서 실행됨

package com.example.coroutinestest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.coroutinestest.databinding.ActivityMainBinding
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
main()
}
fun main() {
CoroutineScope(Dispatchers.IO).launch {
// 병렬로 2개 함수 실행
async { nonSuspendTask1() }
async { nonSuspendTask2() }
}
}
suspend fun nonSuspendTask1() {
delay(3000)
Log.d("TAG", "[nonSuspendTask1] After 3s in (${Thread.currentThread().name})")
delay(3000)
Log.d("TAG", "[nonSuspendTask1] After 6s in (${Thread.currentThread().name})")
Log.d("TAG", "[nonSuspendTask1] END in (${Thread.currentThread().name}) *****")
}
suspend fun nonSuspendTask2() {
delay(3000)
Log.d("TAG", "[nonSuspendTask2] After 1s in (${Thread.currentThread().name})")
delay(3000)
Log.d("TAG", "[nonSuspendTask2] After 4s in (${Thread.currentThread().name})")
Log.d("TAG", "[nonSuspendTask2] END in (${Thread.currentThread().name})*****")
}
}

delay동안 스레드가 suspend되어 다른 함수의 작업을 수행할 수 있기 때문
단, suspend를 사용한다고 해서 무조건 하나의 스레드만 실행되는 것은 아님
여기선 작업이 서로 다른 시간에 일어나기 때문에 1개의 스레드만 실행된 것

4) 정리
- 코루틴 suspend 함수는 스레드를 블락하지 않음
- 따라서 하나의 스레드에 여러개의 코루틴을 실행할 수 있음 특정 작업이 suspend 되고 resume 될 때까지 이 사이에 다른 작업을 수행할 수 있기 때문
- 따라서 suspend는 많은 concurrent(병행) 작업을 지원하면서 블록킹에 대한 메모리를 절약할 수 있음
5) 출처
https://nuritech.tistory.com/16[Kotlin] Coroutine suspend function 은 대체 뭐야?
목차 suspend 는 무엇인가. 사전을 찾아보면, '중지하다' 라는 뜻의 단어다. 그렇다면, coroutine 에서의 suspend keyword 는 무엇을 의미할까? a function that could be started, paused, and resume. 시작하고,..
nuritech.tistory.com
예외 처리
- CoroutineExceptionHandler를 이용하여 코루틴 내부에 try-catch 블록을 만들어 예외 처리 가능
- launch, actor : exception 발생 시 바로 예외가 발생
- async, produce : 중간에 exception이 발생해도 await를 만나야 예외 발생
- Job.cancel()을 제외한 다른 exception이 발생하면 부모의 코루틴까지 모두 취소시킴 이는 structured concurrency를 유지하기 위함으로 CoroutineExceptionHandler를 설정해도 막을 수 없음
- 여러개의 exception이 발생하면 가장 먼저 발생한 exception이 handler로 전달되며 나머지는 무시됨
정리
- 코루틴은 스레드가 아님
- 코루틴은 스레드와 비교하면 메모리를 덜 쓰고 오버헤드가 적은 경량의 비동기 프로그래밍을 수행할 수 있게 하는 모듈로 생각
- 코루틴을 사용하기 위해선 CoroutineScope, CoroutineContext, CoroutineBuilder를 사용해서 코루틴 객체를 만들어서 사용
- CPU 작업이면 Default Dispatchers, I/O 작업이면 IO Dispatchers 사용
- 코루틴 처리 후 값이 나와야 하면 async, 값이 필요 없으면 launch builder 사용
'Native App > 👽Android' 카테고리의 다른 글
web Crawling - Jsoup (0) | 2022.06.27 |
---|---|
kakao login api 사용 - 2 (0) | 2022.06.19 |
kakao login api 사용 - 1 (0) | 2022.06.16 |
#02 SharedPreferences (0) | 2022.06.16 |
우분투 환경에 안드로이드 스튜디오 설치 (0) | 2022.01.24 |
댓글