코루틴이란?

2021. 4. 29. 13:59android

1. 개념

요약하자면, 코틀린에서 코루틴이란 비동기적인 방법으로 콜백 대신 long-running tasks들을 효율적이고 우아하게(?)

처리 할 수 있는 방법입니다. 또한 callback code를 sequential code로 작성이 가능하기 때문에 가독성이 높아지는 효과가 있습니다.

2. 특징

1) 코루틴은 비동기적이다!!

 

: 비동기적이란, 여러개의 일을 하나하나 끝내면서 진행하는 것이 아니라 동시에 병렬적인 일을 수행하여 리턴 값을 받는 것을 의미합니다. 하지만 병렬적으로 일이 처리되는 것 만큼 순서에 대한 보장이 없으며 해당 작업이 언제 끝나는지를 모르기 때문에 result 값을 즉시 사용할 수 있는지 보장되지 않습니다. 

 

2) 코루틴은 non-Blocking이다!!

 

: non-blocking이란, main 또는 UI thread 일에 간섭하지 않는 것을 의미합니다. 안드로이드는 Single Thread로 동작하기

때문에 UI thread에서 무거운 작업을 하게 되면 해당 작업이 끝날 때까지 기다려야 합니다. 때문에 유저는 아무것도 못하고 기다려야하는.. 아주 나쁜 경험을 하게 됩니다. 따라서 long-running task는 Woker thread에게 맡기고 해당 값이 리턴되면 UI thread에게 넘겨줘서 view를 갱신하는 방법으로 진행하도록 합니다.  

 

3) 코루틴은 일처리를 squential한 처리를 위해 suspend function을 사용할 수 있다!!

 

: suspend 키워드는 코루틴에서 해당 메서드를 사용할 수 있도록 marking하는 방법입니다. 해당 키워드를 사용하면

main 또는 UI thread를 blocking하지 않고 해당 function의 result가 준비 될 때까지 "suspend" 하는 것을 의미합니다.

main Thread를 blocking하지 않기 때문에 다른 작업을 할 수 있으며 woker thread에서 suspend 되었던 function이 result가 준비되면 그때서야 값을 리턴받습니다. 

 

3. 코루틴의 구조

1) 코루틴은 기본적으로 모두 job이란 것을 갖고 있습니다. job이란 개발자가 정한 CoroutineScope영역에서 처리되어

지고 있는 전반적인 일들을 관리할 수 있는 것을 의미합니다. 기본적으로 부모-자식의 계층구조를 갖고 있기 때문에 

parent job이 취소되면 child job은 자동으로 취소되도록 관리가 됩니다. (job은 1대 N의 코루틴 동작을 제어할 수 있습니다.) 때문에 scope에 대한 처리를 매번 처리해야하는 번거로움을 피할 수 있습니다.  

 

2) DisPatchers는 코루틴이 동작할 thread를 지정하는 역할입니다.

 

- Dispatchers.Main

: main thread에서 task를 처리하도록 thread를 지정합니다.

 

- Dispatchers.Default 

: Default thread는 JVM 공유 스레드 풀을 사용하고 생성할 수 있는 최대 thread의 개수는 cpu 사양에서 지원하는

core 개수만큼 입니다. 예를 들어 4코어면 cpu를 점유하여 병렬적으로 일처리가 가능한 것은 최대 4개인 것입니다. (최소 2개입니다.)

 

- Dispatchers.IO

: IO thread는 필요에 따라 최대 64개의 thread를 생성하여 일을 처리할 수 있으며, Default thread와

스레드를 공유합니다. 

 

3) Scope는 코루틴이 동작할 수 있도록 공간을 지정하는 것을 의미합니다. 해당 scope는 코루틴의 Job과 Dispatchers에 대한 정보들이 결합되어 생성됩니다. scope가 지정되면 코루틴을 동작할 수 있는 coroutine builder를 생성해야 합니다.

 

- launch coroutine builder 

: launch는 현재 코루틴이 동작을 관리할 수있는 job 객체를 리턴합니다. async와 다른점은 receiver block에서 값이 리턴되도록 코드를 작성하지 않아도 되는 것이며, 오로지 scope의 동작만을 처리할 수 있는 객체가 리턴됩니다. 

 

- async corouitne builder

: async는 지정된 scope에서 값을 리턴 받을 수 있는 defer 객체를 리턴합니다. launch와 다른점은 receiver block에서 리턴되어야 할 값을 마지막에 적어야하며 scope의 result 값을 사용할 수 있습니다. 

 

4. 예제

1) lauch coroutine builder

a와 b 값이 비동기적인 방법으로 값이 셋팅되어야 한다고 가정하면 parent scope안에 각각의 a,b를 위한 scope가 별도로 지정됩니다. a scope에서는 delay가 3만큼 된 이후에야 값이 지정되고 b scope에서는 delay가 1만큼 지나고서 셋팅이 되어야합니다. 따라서 a, b scope 모두가 끝날때까지 parent scope에서는 기다려줘야 합니다. 이를 위한 메서드로 joinAll을 이용하면 a,b scope가 result 값이 준비될 때까지 기다리게 할 수 있습니다. 

 

2) async coroutine builder

이번에는 a,b의 값을 얻기위해서 async builder를 사용하여 defer<T> 객체를 받습니다. receiver block에 보면 lauch와는 다르게 해당 값을 셋팅하는 구조가 아닌 셋팅해야 하는 값을 마지막으로 놓기만 하면 됩니다. 여기서 Defer 객체의 제네릭 타입이 T이기 때문에 receiver block에 마지막 타입으로 defer의 value type 지정이 됩니다. 

하지만 리턴된 값은 10, 5가 아닌 defer 객체입니다. 따라서 해당 값을 얻기 위해서는 aWait() 메서드를 사용해서 얻을 수 있습니다. lauch와는 다르게 joinAll 메서드를 사용하지 않은 이유는 aWait() 메서드가 join 메서드처럼 suspend 키워드가 붙은 빌트인 메서드이기 때문입니다. 따라서 각 scope에서 result가 준비될 때까지 suspend 됩니다. 

 

3) Job -1

job 객체를 별도로 생성하고 이를 scope에서 초기화 시켜줍니다. 그러면 지정된 스코프의 작업을 스코프 밖에 있는 job

객체가 관리할 수 있게 됩니다. parent scope 안에 child scope인 launch 두개가 존재합니다. 각각 일정한 딜레이를 갖는 반복분을 통해서 로그를 남기는데 two child scope에서 외부에 있는 job을 통해 parent scope를 중단하면 어떻게

될까요? 위의 개념정리를 통해서 다뤘던 것처럼 scope는 기본적으로 parent - child 계층구조를 갖습니다. 때문에 parent scope가 cancel되면 child scope 또한 cancel되는 것을 볼 수 있습니다. 

 

4) Job -2

만약 중첩된 스코프를 갖는 구조일때에도 부모 job을 사용하면 모든 scope가 종료가 될까요?

 

그렇지 않습니다. 위 로그에서 보여지는 것처럼 중첩된 scope가 모두 로그를 출력하게 됩니다. 이는 중첩된 scope지만 결국엔 개별적인 scope이기 때문에 parent scope에서 초기화 된 job의 영향을 받지 않습니다. 때문에 외부에서 선언된 job을 각 scope에서 초기화 해야 합니다.

 

로그에서처럼 각 scope가 외부의 job객체를 통해서 모두 중단되는 것을 볼 수 있습니다. 

'android' 카테고리의 다른 글

디자인 패턴의 차이점  (0) 2021.10.11
2021 네이버웍스 인턴 후기  (3) 2021.09.13
Glide란?  (0) 2021.01.18
리싸이클러뷰 멀티 뷰타입 사용해보기  (0) 2021.01.10
recyclerView와 dataBinding 같이 사용하기  (1) 2021.01.03