Coroutine

Updated:

Coroutine - kotlin

  • 개념 : 코루틴(coroutine)은 루틴의 일종으로서, 협동 루틴이라 할 수 있다(코루틴의 “Co”는 with 또는 togather를 뜻한다). 상호 연계 프로그램을 일컫는다고도 표현가능하다. 루틴과 서브 루틴은 서로 비대칭적인 관계이지만, 코루틴들은 완전히 대칭적인, 즉 서로가 서로를 호출하는 관계이다. 코루틴들에서는 무엇이 무엇의 서브루틴인지를 구분하는 것이 불가능하다.(위키피디아)


  • 서로 협력해서 실행을 주고 받으면서 작동하는 여러 서브루틴을 말한다.

    • 코루틴은 서브루틴의 확장개념이라고 볼 수 있다.
    • 함수와 다르게 시작과 끝이 아닌 로직의 어느 부분에서도 시작과 종료가 이루어 질 수 있다.


    Coroutine 과 Subroutine 의 비교 - 이미지출처


  • VS Threads - 차이점 참조


    • Thread

      OS의 Native Thread에 직접 링크되어 동작하여 많은 시스템 자원 사용

      Thread간 전환 시에도 CPU 상태 체크가 필요하므로 그만큼의 비용 발생

      마음대로 stop시 죄악으로 취급받을 수 있다.

    • Couroutine

      즉시 실행이 아니고, OS의 영향을 받지 않아 cost가 많이 들지 않음

      전환 시 Context Switch 가 일어나지 않음

      루틴 실행, 종료 여부를 직접 지정 가능

      ​ -> 작업 전환 시 시스템의 영향을 받지 않아 그에 따른 비용 발생 X(semaphores, mutexes 등 필요 X)

      동시성은 제공하지만 병렬은 제공하지 않음




  • kotlin

    • CoroutineContext & CoroutineScope - 참조

      • CoroutineContext

        코루틴을 어떻게 처리할 것인지에 대한 여러가지 Element를 포함 -> Element의 집합

        Interface로, 코루틴에 대한 설정 요소를 등록하고, Scope의 속성이된다.

        • 4가지 메소드 존재 - <get(), folde(), plus(), minusKey()>

          3가지 종류의 구현체로 존재

        • EmptyCoroutineContext - Default, Singleton 객체 사용

        • CombinedContext : 2개 이상의 Context가 명시되면 Context간 연결을 위한 컨테이너 역할을 하는 Context

        • Element : Context의 각 Element들도 CoroutineContext를 구현함


      • CoroutineScope

        CoroutineContext 하나만 멤버속성으로 정의하고 있는 인터페이스

        public interface CoroutineScope {
            /**
             * Context of this scope.
             */
            public val coroutineContext: CoroutineContext
        }
        

        코루틴 빌더들은 CoroutineScope의 확장함수로 CoroutineScope의 함수로 호출됨

        코루틴 빌더를 통해 생성되는 코루틴들은 CoroutineScope의 멤버속성에 기반으로 생성됨

        명시해 주지 않으면 default인 EmptyContext로 할당

        • GlobalScope랑 같다는 의미 이며 Activity가 아닌, Application의 생명주기에 종속된다는 의미
        • 따라서 Activity에 종속적으로 사용하려면 Activity에 Scope를 초기화 설정 해줘야함. - 참조
        class MainActivity : AppCompatActivity(), CoroutineScope {    // CoroutineScope 인터페이스 구현
            lateinit var job :Job
            override val coroutineContext :CoroutineContext	
        								get() = Dispatchers.Main + job	
                      
            // Activity 생성 시 job 할당
            override fun onCreate(savedInstanceState: Bundle?) {
            	super.onCreate(savedInstanceState)
                job = Job()
            }
                  
            // Activity 소멸 시 job 소멸
            override fun onDestroy() {
          	super.onDestroy()
            	job.cancel() // Cancel job on activity destroy. After destroy all children jobs will be cancelled automatically
            }
        }
        



  • Dispatcher

    • CoroutineContext의 주요요소
    • CoroutineContext을 상속 받아 어떤 스레드를 이용하여 어떻게 동작할 것인지 미리정의
      Dispatchers.Default : CPU 사용량이 많은 작업에 사용. Main Thread 에서 작업하기에는 너무 긴 작업들에게 알맞음
        
      Dispatchers.IO : 네트워크, 디스크 사용시 사용. File을 읽고, 쓰고, Socket을 읽고, 쓰고 작업을 멈추는 것에 최적화 되어있음
        
      Dispatchers.Main : Android 의 경우 UI 스레드를 사용
    

    이외에 코루틴 공식 문서에 Dispatchers.Unconfined도 존재

    • 다른 Dispatcher 와 달리 특정 스레드(or 풀) 지정하지 않음


  • 코루틴 사용 순서

    • 사용할 Dispatcher 결정
    • Dispatcher 이용하여 CoroutineScope 생성
    • CoroutineScope 의 launch 또는 async에 수행할 코드 블록을 넘기면 됨
    • Basick coroutine code
    CoroutineScope(Dispatchers.Main).launch{
      //foreground task
    }
      
    CoroutineScope(Dispatchers.Main).launch(Dispatchers.Default){
      //CoroutineContext change so working task by converting to Background
    }
    


    • Background task
    val scope = CoroutineScope(Dispatchers.Main)
      
    CoroutineScope(Dispatchers.Default).launch{
      //Background task by new CoroutineScope
    }
      
    scope.launch(Dispatchers.Default){
      //CoroutineScope는 유지되면서 작업이 처리되는 스레드만 변경됨
    }
    
    • 제어 범위가 다를 시 발생하는 현상
      • 멈추지 않는 내부 coroutine block
    val scope = CoroutineScope(Dispatchers.Main)
      
    val job = scope.launch{
      //...
     	CoroutineScope(Dispatchers.Main).launch{
        //scope coroutine이 취소 되어도 지속 수행
      }
      //...
    }
      
    job.cancel()
    


  • Coroutine 제어를 위한 주요 keyword - 참고

    • launch() - Job

      • Coroutine block은 job 객체를 반환
      val job : Job = launch{
      //...
      }
      
      • launch() 함수로 정의된 코루틴 블록은 즉시 수행되며, 반환 받은 Job객체는 해당 블록을 제어 할 수 있지만, 결과를 반환하진 않음 - 결과 반환은 async() 이용
    • async() - Deferred

      • Deferred 객체를 반환
      val deferred : Deferred<T> = async{
      	//...
      	T //Result
      }
      
      • Deferred 객체를 이용해 제어가 가능하고, 동시에 결과값을 반환받을 수 있음
    • 지연실행 - start 인자에 CoroutineStart.LAZY 를 사용하면 해당 코루틴 블록은 지연 실행

      val job = launch(start = CoroutineStart.LAZY){
      // ... 
      }
          
      or
          
      val deferred = async(start = CoroutineStrat.LAZY){
      // ...
      }
      
    • runBlocking()

      • 코드 블록이 작업을 완료하기를 기다림
      runBlocking{
      //...
      }
      
      • runBlocking() 함수로 시작된 블록은 아무런 추가함수 호출 없이 해당 블록이 완료될때까지 기다릴 수 있음
      • runBlocking 이 사용하는 스레드는 현재 runBlocking() 함수가 호출된 스레드
      • Android 의 경우 runBlocking() 함수를 메인 스레드 에서 호출하여 시간이 오래 걸리는 작업을 수행하는 경우 ANR이 발생할 위험이 있으므로 주의해야함





  • CoroutineScope vs GlobalScope - 참조

Updated: