본문 바로가기

[Kotlin] 코루틴 #2 - CoroutineContext와 CoroutineScope란?

코루틴 #2 - CoroutineContext와 CoroutineScope란?

CoroutineContext와 CoroutineScope란 무엇인가?

 

Coroutine 이전 글

  1. 코루틴 #1 - 기본

 

 

CoroutineContext

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

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

CoroutineContext는 interface인데, 그 구성을 한번 살펴보기 위해 CoroutineContext.kt파일에서 가져왔습니다.

public interface CoroutineContext {
  /**
   * Returns the element with the given [key] from this context or `null`.
   * Keys are compared _by reference_, that is to get an element from the context the reference to its actual key
   * object must be presented to this function.
   */
  public operator fun <E : Element> get(key: Key<E>): E?
  /**
   * Accumulates entries of this context starting with [initial] value and applying [operation]
   * from left to right to current accumulator value and each element of this context.
   */
  public fun <R> fold(initial: R, operation: (R, Element) -> R): R
  /**
   * Returns a context containing elements from this context and elements from  other [context].
   * The elements from this context with the same key as in the other one are dropped.
   */
  public operator fun plus(context: CoroutineContext): CoroutineContext = ...impl...
  /**
   * Returns a context containing elements from this context, but without an element with
   * the specified [key]. Keys are compared _by reference_, that is to remove an element from the context
   * the reference to its actual key object must be presented to this function.
   */
  public fun minusKey(key: Key<*>): CoroutineContext
}

/**
 * Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
 * Keys in the context are compared _by reference_.
 */
public interface Key<E : Element>

/**
 * An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
 */
public interface Element : CoroutineContext {
  /**
   * A key of this coroutine context element.
   */
  public val key: Key<*>
  
  ...overrides...
}

 

CoroutineContext 인터페이스 내부에는 4가지의 메서드가 존재합니다. 

public operator fun <E : Element> get(key: Key<E>): E?

1) get() - 연산자(operator) 함수의 의미로, 매개변수로 주어진 key에 해당하는 Context 요소를 반환하는 함수

public fun <R> fold(initial: R, operation: (R, Element) -> R): R

2) fold() - 초기값(initalVlaue)을 시작으로 제공된 병합 함수(operation)를 이용해서 대상 컨텍스트 요소들을 병합한 후 결과를 반환하는 함수

public operator fun plus(context: CoroutineContext): CoroutineContext = ...impl...

2) plus() - 현재(기존) Context와 파라미터로 주어진 context가 갖는 요소(Element)들을 모두 포함하는 새로운 Context를 반환

public fun minusKey(key: Key<*>): CoroutineContext

2) minusKey() - 현재(기존) Context에서 주어진 key를 갖는 요소(Element)를 제외한 새로운 Context를 반환

 

Key / Element란?

여기서 Key에 해당하는 요소(Element)란, CoroutineContext를 구성하는 요소를 뜻하는데

CoroutineId, CoroutineName, Dispatchers, CoroutineInterceptor, CoroutineExceptionHandler 등 의미

key -> Element 타입을 제네릭으로 갖고, key를 기반으로 Context에 Element를 등록하는 역할

Element -> CoroutineContext를 상속, key를 멤버속성으로 포함한다

Element는 CoroutineContext를 상속한다는 점을 알아두면 CoroutineScope를 이해하는데 도움이 된다

 

CoroutineContext 구현체

CoroutineContext는 인터페이스로 이를 구현한 구현체는 기본적으로 3가지의 종류가 존재

1) EmptyCoroutineContext - Singleton(싱글톤), Default Context
      Context를 명시하지 않을 경우 기본적으로 적용되는

      GlobalScope에 사용된 Context로, GlobalScope == launc{ }는 같은 싱글톤 EmptyContext를 사용하는 의미이다

2) CombinedContext - 두개 이상의 컨텍스트가 명시되면 Context간 연결을 위한 컨테이너역할의 Context.
      * 명시란? - launch(Dispatchers.Main + job) 이렇게 코루틴 생성 시 프로퍼티를 설정한다는 의미

3) Element - Context의 각 요소(Element)들도 CoroutineConext를 구현한다고 위에서 언급한 의미
         

CombinedContext 이해 위한 사진

이미지는 코루틴 빌더(launch)로 코루틴을 생성하는 예시들을 나열한 사진인데,

launch() 함수 내부 CoroutineContext를 추가하는 건데 Element는 Context의 구현체로, 즉 Element가 개별적인 Context이다,
Element끼리 + 연산자로 설정을 하는데 +는 Context메서드 중 하나인 plus() 함수로 Element들을 합쳐 새로운 Context를 반환하는 것으로 보면된다
Element + Element + ...는 결국 plus() 함수의 연속으로 하나로 병합된 Context를 만들어내는 점.

위 이미지에서 주황색 테두리는 Context구현체 중 하나인 CombinedContext로서 Context들 간을 묶어주는 하나의 CoroutineContext가 되는 개념

 

 

CoroutineScope  

CoroutineScope는 코루틴 블럭(영역)을 생성하는 역할

하나의 CoroutineConext 멤버속성만 갖는 인터페이스

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

코루틴 빌더들은 CoroutineScope의 확장함수로, 이 CoroutineScope의 함수로 호출되는데,

    * 코루틴 빌더들? - 코루틴 빌더(launch / actor) + 스코프 빌더(async / produce)

코루틴 빌더를 통해 생성되는 코루틴들은 CoroutineScope의 멤버속성(coroutineContext)에 기반으로 생성된다.

위에서 애기했듯이 CoroutineContext를 명시해주지 않으면 기본적으로 EmptyContext로 할당된다.
그 의미는 GlobalScope랑 같다는 의미면서 Activity가 아닌, Application의 생명주기에 종속된다는 의미이다.

그래서 기본적으로 설정된 Application에 종속적인 EmptyContext를 사용하지 않고 Activity에 종속적으로 사용하려면 Activity에 Scope를 초기화 설정을 해줘야 한다

class MainActivity : AppCompatActivity(), CoroutineScope {    // CoroutineScope 인터페이스 구현
    lateinit var job :Job
    // CoroutineScope 인터페이스 멤버속성 구현
    override val coroutineContext :CoroutineContext	
    	// Context 함수 get() - Context(Element)들을 합쳐 새로운 Context 반환
	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
    }
}

 

명시하지 않은 CoroutineScope와 GlobalContext가 같은 Context라는 걸 보여주기 위한
GlobalScope 구조와 EmptyCoroutineContext 구조

// GlobalScope 구조, EmptyCoroutineContext를 구현한 점
object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

// EmptyCoroutineContext의 구조, object - Singleton 의미
@SinceKotlin("1.3")
public object EmptyCoroutineContext : CoroutineContext, Serializable {
    private const val serialVersionUID: Long = 0
    private fun readResolve(): Any = EmptyCoroutineContext

    public override fun <E : Element> get(key: Key<E>): E? = null
    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial
    public override fun plus(context: CoroutineContext): CoroutineContext = context
    public override fun minusKey(key: Key<*>): CoroutineContext = this
    public override fun hashCode(): Int = 0
    public override fun toString(): String = "EmptyCoroutineContext"
}

GlobalScope.launch{}로 실행한 코루틴은 Application이 종료할 때 까지 죽지않는 다는 점이 중요한 포인트

 

참고 문서