Kotlin 협 정 은 도대체 어떻게 스 레 드 를 전환 합 니까?

안 드 로 이 드 개발 분야 에서 kotlin 이 인 기 를 끌 면서 협 정 은 각 프로젝트 에서 의 응용 도 점점 광범 위 해 졌 다.
그런데 협 정 은 도대체 뭘 까요?
협 정 은 사실 오래된 개념 으로 이미 매우 성숙 하 였 으 나,모두 가 그것 의 개념 에 대해 줄곧 각종 의문 을 가지 고 있어,많은 사람들 이 잇달아 말 하고 있다.
어떤 사람 은 협 정 이 경량급 의 스 레 드 라 고 말 하고,또 어떤 사람 은 kotlin 협 정 이 사실은 본질 적 으로 스 레 드 전환 방안 이 라 고 말한다.
분명히 이것 은 초보 자 에 게 그다지 우호 적 이지 않다.한 가지 물건 이 무엇 인지 잘 모 를 때 왜,어떻게 하 는 단계 에 들 어가 기 어렵다.
본 고 는 주로 이 문제 에 대답 하 는데 주로 다음 과 같은 내용 을 포함한다.
1.협 정 에 대한 선행 지식
2.협 정 은 도대체 무엇 입 니까?
3.kotlin 협회 의 기본 개념,걸 기 함수,CPS 변환,상태 기 등
상기 문 제 는 사고 지도 로 요약 하면 다음 과 같다.

1.선행 지식
1.1CoroutineScope도대체 무엇 입 니까?CoroutineScope즉,협 정 운행 의 역할 영역 으로 소스 코드 는 매우 간단 하 다.

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}
이 를 통 해 알 수 있 듯 이CoroutineScope의 코드 는 매우 간단 하고 주요 역할 은 제공CoroutineContext,협 정 운행 의 문맥 이다.
우리 가 흔히 볼 수 있 는 실현 은GlobalScope,LifecycleScope등 이 있다.
1.2ViewModelScopeGlobalScope는 어떤 차이 가 있 습 니까?

public object GlobalScope : CoroutineScope {
    /**
     *    [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }
양자 의 코드 는 모두 매우 간단 해서 위 에서 볼 수 있다
1.ViewModelScope되 돌아 오 는 것 은GlobalScope의 공 실현 이다.
2.CoroutineContextViewModelScopeCoroutineContextJob를 추가 했다.
저희 가 먼저 간단 한 코드 를 보 겠 습 니 다.

	fun testOne(){
		GlobalScope.launch {
            print("1:" + Thread.currentThread().name)
            delay(1000)
            print("2:" + Thread.currentThread().name)
        }
	}
	//     :DefaultDispatcher-worker-1
    fun testTwo(){
        viewModelScope.launch {
            print("1:" + Thread.currentThread().name)
            delay(1000)
            print("2:" + Thread.currentThread().name)
        }
    }
    //     : main
위의 두 가지Dispatcher가 협 정 을 시작 한 후에 현재 스 레 드 이름 을 인쇄 하 는 것 은 다 릅 니 다.하 나 는 스 레 드 탱크 의 스 레 드 이 고 하 나 는 메 인 스 레 드 입 니 다.ScopeViewModelScope를 첨가 한 이유 다.
우 리 는 협 정 은CoroutineContext스케줄 러 를 통 해 스 레 드 전환 을 제어 한 다 는 결론 을 얻 을 수 있다.
1,3 스케줄 러 가 뭐 예요?
사용 상 스케줄 러 는 우리 가 사용 하 는 것Dispatchers.Main.immediate,Dispatchers,Dispatchers.Main등 이다.
역할 상 스케줄 러 의 역할 은 협 정 운행 을 제어 하 는 스 레 드 이다.
구조 적 으로 볼 때Dispatchers.Default의 부 류 는Dispatcher.IO이 고 그 다음 에Dispatchers에 계승 된다.
그들의 유형 구조 관 계 는 다음 과 같다.

이것 도ContinuationInterceptorCoroutineContext에 가입 할 수 있 는 이유 이 며,Dispatchers조작 부호 가 증가 하 는 것 을 지원 하 는 이유 이다.
1.4 차단기 란 무엇 인가
이름 에서 쉽게 알 수 있 듯 이CoroutineContext즉 협 정 차단기 입 니 다.먼저 인 터 페 이 스 를 보 세 요.

interface ContinuationInterceptor : CoroutineContext.Element {
    // ContinuationInterceptor   CoroutineContext    Key
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    /**
     *    continuation
     */
    fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>

    //...
}
위 에서 두 가지 정 보 를 추출 할 수 있다.
1.차단기+는 하나의 예 이기 때문에 여러 개의 차단 기 를 추가 할 때 한 개 만 적 용 됩 니 다.
2.우 리 는 모두 알 고 있다.ContinuationInterceptorKey방법 을 호출 하면 그Continuation수식 함수 의 코드 블록 을 실행 할 것 이다.만약 에 우리 가 미리 차단 하면 다른 일 을 할 수 있 지 않 을 까?이것 이 바로 스케줄 러 가 스 레 드 를 전환 하 는 원리 이다.
위 에서 우 리 는 이미Continuation#resumeWith()지정 협 정 을 통 해 운행 하 는 스 레 드 를 소개 하 였 으 며,suspend협 정 이 회복 되 기 전에 차단 하여 스 레 드 를 전환 하 였 다.
이러한 선행 지식 을 가지 고 우 리 는 협 정 이 작 동 하 는 구체 적 인 절 차 를 살 펴 보고 협 정 전환 스 레 드 소스 코드 의 구체 적 인 실현 을 명 확 히 한다.
2.협 정 스 레 드 전환 소스 코드 분석
2.1Dispatchers방법 분석
우 리 는 먼저 협 정 이 어떻게 작 동 하 는 지,어떤 매개 변수 가 들 어 왔 는 지 살 펴 보 자.

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
총 3 개의 인자 가 있 습 니 다:
1.들 어 오 는 협정 상하 문
2.interceptContinuation시동 기 는 매 거 진 클래스 로 서로 다른 시작 방법 을 정 의 했 습 니 다.기본 값 은launch입 니 다.
3.CoroutinStart바로 우리 가 들 어 온 협의 체 이 고 진정 으로 실행 해 야 할 코드 이다.
이 코드 는 주로 두 가지 일 을 했다.
1.새로운 조합CoroutineStart.DEFAULT2.Continuation 만 들 기
2.1.1 새로운 조합block

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}
위 에서 아래 정 보 를 추출 할 수 있 습 니 다:
1.CoroutineContext방법 이 들 어 오 는CoroutineContext방법 과launch중의context방법 을 조합 합 니 다.
2.CoroutineScope에 차단기 가 없 으 면 기본 차단기,즉context가 들 어 옵 니 다.이것 은 우리 가 차단기 에 들 어 오지 않 았 을 때 기본 스 레 드 전환 효과 가 있 는 이 유 를 설명 합 니 다.
2.1.2 하나 만 들 기combined

val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
기본 적 인 상황 에서 우 리 는 하 나 를 만 들 것 이다Dispatchers.Default주의해 야 할 것 은 이것Continuation은 사실 우리 협의 체 의StandloneCoroutine,즉 성공 후의 반전 이지 협의 체 자체 가 아니다.
그리고 호출coroutine은 협 정 이 시작 되 었 음 을 나타 낸다.
2.2 협상 의 시작

public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    initParentJob()
    start(block, receiver, this)
}
이 어 협 정 을 시작 하기 위해completecoroutine.start를 호출 했다.기본 적 인 상황 에서 호출 된 것 은CoroutineStart이다.
층 층 이 호출 되 어 마지막 에 도 착 했 습 니 다.

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        //        Coroutine
        createCoroutineUnintercepted(receiver, completion)
            //     ,     
            .intercepted()
            //    resumeWith         
            .resumeCancellableWith(Result.success(Unit))
    }
여기 가 바로 협 정 이 시작 하 는 핵심 코드 입 니 다.짧 지만 세 가지 절 차 를 포함 합 니 다.
1.협의 체 창설start2.차단 만 들 기CoroutineStart.Default,즉Continuation3.실행Continuation방법
2.3 협의 체 창설DispatchedContinuation호출DispatchedContinuation.resumeWith은 우리 의 협의 체 즉ContinuationcreateCoroutineUnintercepted로 전환 시 킬 것 이다.그것 은suspend block이 고 계승Continuation이다.SuspendLambda방법 은 소스 코드 에서 구체 적 인 실현 을 찾 을 수 없 지만 협의 체 코드 를 역 컴 파일 하면 진정한 실현 을 볼 수 있다.
자세 한 내용 은 다음 과 같 습 니 다:바이트 코드 역 컴 파일
2.4 창설ContinuationImpl

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this

//ContinuationImpl
public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }     

//CoroutineDispatcher
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
      DispatchedContinuation(this, continuation)  
위 에서 아래 의 정 보 를 추출 할 수 있다
1.createCoroutineUnintercepted확장 방법 입 니 다.마지막 으로DispatchedContinuation방법 으로 호출 됩 니 다.
2.interepted에서ContinuationImpl.intercepted를 이용 하여 현재 차단 기 를 가 져 옵 니 다.
3.현재 차단 기 는intercepted이기 때문에 최종 적 으로 하나CoroutineContext로 돌아 갑 니 다.우 리 는 사실 이 를 이용 하여 스 레 드 전환 을 실현 합 니 다.
4.우 리 는 협의 체CoroutineDispatcherDispatchedContinuation에 전래 시 켰 는데 여기 서 실 용적 으로Continuation기능 의 강 화 를 실현 했다.

여기 서 분명 하 다.DispatchedContinuation을 통 해 기 존의 협 정 을 장식 하고 에서 스케줄 러 처리 스 레 드 전환 을 통 해 기 존의 논리 에 영향 을 주지 않 고 기능 의 강 화 를 실현 한다.
2.5 차단 처리

//DispatchedContinuation
    inline fun resumeCancellableWith(
        result: Result<T>,
        noinline onCancellation: ((cause: Throwable) -> Unit)?
    ) {
        val state = result.toState(onCancellation)
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_CANCELLABLE
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_CANCELLABLE) {
                if (!resumeCancelled(state)) {
                    resumeUndispatchedWith(result)
                }
            }
        }
    }
시작 할 때 호출DispatchedContinuation하 는DispatchedContinuation방법 에 대해 말씀 드 렸 습 니 다.
이 안에서 하 는 일 도 간단 하 다.
1.스 레 드 전환 이 필요 하 다 면DispatchedContinuation방법 을 호출 합 니 다.여기resumeCancellableWithdispatcher.dispatcher을 통 해 추출 되 었 습 니 다.
2.스 레 드 전환 이 필요 하지 않 으 면 기 존 스 레 드 를 직접 실행 하면 됩 니 다.
2.5.2 스케줄 러 의 구체 적 인 실현
우 리 는 먼저 명확 하 게dispatcherCoroutineConext을 통 해 추출 한 것 이 고 이것 도 협 정 문맥 작용 의 표현 이다.CoroutineDispatcher공식 적 으로 네 가지 실현 을 제공 했다.CoroutineContext,CoroutineDispater,Dispatchers.Main,Dispatchers.IO우리 함께 간단하게Dispatchers.Default의 실현 을 봅 시다.

internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
    public constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)

    //...

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        //        Handler     
        handler.post(block)
    }
}
볼 수 있 듯 이 사실은Dispatchers.Unconfined으로 메 인 스 레 드 로 전환 되 었 다.Dispatchers.Main를 사용 해도 마찬가지 입 니 다.스 레 드 풀 로 바 뀌 었 을 뿐 입 니 다.

위 에서 보 듯 이 사실은 장식 모델 이다.
1.호출handler방법 으로 스 레 드 전환
2.전환 완료 후 호출Dispatcers.IO방법 으로 진정한 협 정 체 를 실행 합 니 다.
3CoroutinDispatcher.dispatch스 레 드 를 어떻게 전환 합 니까?
위 에서 우 리 는 협 정 스 레 드 스케줄 의 기본 원리 와 실현 을 소개 하 였 으 며,아래 에서 우 리 는 몇 가지 작은 문제 에 대답 하 겠 다.
우 리 는DispatchedTask.run함수 가 끊 어 진 후에 한동안 기 다 렸 다가 다시 회복 할 것 이라는 것 을 안다.
이 안에 도 스 레 드 의 전환 과 관련 이 있 을 것 이 라 고 상상 할 수 있 습 니 다.구체 적 으로 어떻게 실현 되 었 습 니까?

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
delay의 코드 도 간단 하여 위 에서 다음 과 같은 정 보 를 추출 할 수 있다.delay의 전환 도 차단 기 를 통 해 이 루어 졌 고 내 장 된 차단 기 는 동시에Dealy인 터 페 이 스 를 실현 했다.
구체 적 인 실현 을 살 펴 보 겠 습 니 다.

internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        //        Handler       ,     continuation        
        val block = Runnable {
            with(continuation) { resumeUndispatched(Unit) }
        }
        handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
        continuation.invokeOnCancellation { handler.removeCallbacks(block) }
    }

    //..
}
1.알 수 있 듯 이 사실delay을 통 해 지연 효 과 를 실현 한 것 이다.
2.시간 이 되면Delay방법 으로 협상 을 재 개 합 니 다.
3.만약 에 우리 가 사용 하 는 것 이handler.postDelayed이 라면 효과 도 똑 같 습 니 다.다른 것 은 지연 효 과 는 스 레 드 전환 을 통 해 이 루어 진 것 입 니 다.
4.resumeUndispatched스 레 드 를 어떻게 전환 합 니까?
우 리 는 협 정 체 내 에서Dispatcher.IO방법 으로 간단 하고 편리 한 스 레 드 전환 을 통 해 동기 화 된 방식 으로 비동기 코드 를 쓸 수 있다.이것 도withContext협 정의 주요 장점 중 하나 이다.

fun test(){
        viewModelScope.launch(Dispatchers.Main) {
            print("1:" + Thread.currentThread().name)
            withContext(Dispatchers.IO){
                delay(1000)
                print("2:" + Thread.currentThread().name)
            }
            print("3:" + Thread.currentThread().name)
        }
    }
    //1,2,3     main,DefaultDispatcher-worker-1,main
이 코드 가 스 레 드 를 전환 한 후에 다시 전환 하 는 작업 을 한 것 을 알 수 있 습 니 다.우 리 는 두 가지 문 제 를 제기 할 수 있 습 니 다.
1.withContext스 레 드 를 어떻게 전환 합 니까?
2.kotin안의 협의 체 가 끝 난 후에 스 레 드 는 어떻게withContext로 전환 합 니까?

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {  
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        //     context
        val oldContext = uCont.context
        val newContext = oldContext + context
        ....
        //    Dispatcher,    
        val coroutine = DispatchedCoroutine(newContext, uCont)
        coroutine.initParentJob()
        //DispatchedCoroutine   complete  
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

private class DispatchedCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
	// complete     
    override fun afterCompletion(state: Any?) {
        afterResume(state)
    }

    override fun afterResume(state: Any?) {
        //uCont     ,context    context,             
        uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
    }
}
이 코드 는 사실 매우 간단 해서 다음 과 같은 정 보 를 추출 할 수 있다.
1.withContext사실은 한 층Dispatchers.Main포장 이 고 마지막 에withContext로 호출 되 었 습 니 다.이것 은Api뒤의 절차 와 같 습 니 다.우 리 는 계속 따라 가지 않 겠 습 니 다.
2.들 어 오 는startCoroutineCancellable외부 차단 기 를 덮어 하나의launch를 생 성하 기 때문에 스 레 드 전환 이 가능 합 니 다.
3.contextnewContext협의 체 의 생 성 함수 로 서 협의 체 의 실행 이 완료 되면DispatchedCoroutine으로 되 돌아 갑 니 다.
4.complete에서 들 어 오 는afterCompletion은 부 협 정 이 고 그의 차단 기 는 외부 차단 기 이기 때문에 원래 의 스 레 드 로 전환 합 니 다.
총결산
본 고 는 주로DispatchedCoroutine협 정 이 어떻게 스 레 드 를 전환 하 는 지 에 대한 이 문 제 를 대답 하고 소스 코드 를 분석 했다.
쉽게 말 하면 다음 과 같은 절 차 를 포함한다.
1.uContkotlin를 추가 하고 실행 할 협 정 을 지정 합 니 다.
2.시작 할 때CoroutineContextDispatcher로 만 들 고 호출suspend block생 성Continuation3.intercepted바로 기 존의 협 정 에 대한 장식 이다.여기 서 호출DispatchedContinuation스 레 드 전환 임 무 를 완성 한 후에DispatchedContinuation장 식 된 협 정 은 협 정 체 내의 코드 를 집행 할 것 이다.
사실Dispatcher협 정 은 장식 기 모델 로 스 레 드 전환 을 실현 하 는 것 이다.
보아하니 많은 코드 가 있 는 것 같 지만 진정한 사고방식 은 사실 매우 간단 하 다.이것 은 아마도 디자인 모델 의 작용 일 것 이다
마지막.
안 드 로 이 드 개발 과 관련 된 학습 문서,면접 문제,안 드 로 이 드 핵심 노트 등 문 서 를 공유 하고 여러분 의 학습 향상 에 도움 이 되 기 를 바 랍 니 다.참고 할 것 이 있 으 면 저 에 게 CodeChina 주 소 를 직접 가 주 십시오.https://codechina.csdn.net/u012165769/Android-T3 방문 하여 열람 하 다.만약 본문 이 당신 에 게 도움 이 된다 면,좋아요 모음 집 을 클릭 하 세 요~
Kotlin 협 정 이 스 레 드 를 어떻게 전환 하 는 지 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 협 정 스 레 드 전환 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

좋은 웹페이지 즐겨찾기