Kotlin 협업 -runBlocking과coroutineScope에서부터

8958 단어 Kotlin필기Android
협정에 관해서 우리는 많이 논술하지 않으니 상세한 내용은 공식 문서를 보십시오. 본고는 runBlockingcoroutineScope만 이야기합니다.
runBlocking
먼저 runBlocking 문서에서 이 함수를 어떻게 설명하는지 살펴보겠습니다.
Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests. 새 프로토콜을 실행하고 현재 인터럽트 가능한 라인을 막아서 프로토콜이 실행될 때까지 이 함수는 프로토콜에서 사용할 수 없습니다. 이 함수는 일반 인터럽트 코드를 연결하는 데 사용되며, 마운트 스타일 (suspending style) 로 작성된 라이브러리에 사용되며, 주 함수와 테스트에 사용됩니다.
이 단락의 말은 어떻게 이해합니까?이것은 suspend 수식자부터 말하자면, 협정 사용에서 이 수식자를 사용하여 하나의 함수를 수식할 수 있으며, 이 함수는 마운트 함수로 협정에서 운행된다는 것을 나타낸다.함수를 끊으면 라인이 막히지 않지만, 협정이 끊기고 협정에서만 사용할 수 있습니다.마운트 함수는main 함수에서 호출될 수 없습니다. 그러면 어떻게 디버깅합니까?참, 바로 runBlocking 함수를 사용합니다!
우리는 runBlocking 함수를 사용하여 주 프로토콜을 구축하여 프로토콜 코드를 디버깅할 수 있습니다.당신은 협정에 어떤 우세가 있는지 물어볼 수 있습니까?내가 그를 알아야 한다고?공식적으로 든 작은 예를 인용해 보자.
import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) { //        
        launch {
            delay(1000L)
            print(".")
        }
    }
}

협정 10W개를 만들고 1초 후에 동시에 점 하나를 인쇄합니다. 더 이상 말하지 않아도 됩니다. 라인으로 이루어지면 무슨 일이 일어날지 아시죠?
사용 방법:
fun main() = runBlocking {
    // this: CoroutineScope
    launch {
        //   runBlocking            
        delay(1000L)
        println("World! ${Thread.currentThread().name}")
    }
    println("Hello, ${Thread.currentThread().name}")
}

총괄:runBlocking 방법은 일반적인 차단 라인에서 함수를 마운트하기 위해 새로운 협정을 열 수 있으며, 협정에서 launch 방법을 호출하여 하위 협정을 열어 백엔드 차단 작업을 실행할 수 있다.
만약 우리가 일반적인 라인에서 이 방법을 운행한다면:
fun main2_1() {
    runBlocking {
       launch {
            //               
            delay(3000L)
            println("World!")
        }
    }
    println("Hello,") 
}

runBlocking은 메인 라인을 막습니다. runBlocking 내부의 모든 하위 작업이 실행될 때까지 다음 동작을 계속 수행할 수 있습니다.
협업 과정 중 시간 소모 임무를 집행하다
알겠습니다. runBlocking 함수를 통해 협정을 만드는 방법을 알았습니다. 그러면 협정을 어떻게 이용하여 시간 소모 임무를 처리해야 합니까?우리의 첫 번째 실례 코드를 실행합니다. 인쇄 결과를 통해 두 번의 인쇄가 서로 다른 협정에서 실행되고 있음을 알 수 있습니다. 왜 launch 함수를 호출하면 새로운 협정을 만들 수 있는지 궁금할 수 있습니다.launch 메소드의 소스를 살펴보겠습니다.
fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> Unit
): Job (source)

방법 서명에서 알 수 있듯이 launch 방법은CoroutineScope의 확장 함수이다. 이 방법은 현재 라인을 막지 않는 상황에서 새로운 프로토콜을 시작하고 프로토콜의 인용을 Job으로 되돌려준다.생성이 취소된 Job에서는 협약이 취소된다.
기본적으로 launch의 코드는 즉시 실행됩니다.주의 방법 서명 중의 두 번째 매개 변수인 start을 참고하십시오. 이 매개 변수를 수정하여 서로 다른 서브 프로토콜 시작 방식을 변경할 수 있습니다. 자세한 내용은 문서Coroutine Start를 보십시오.
CoroutineScope 소개
launch 방법을 이해한 후, 도대체 Coroutine Scope가 무엇인지 살펴보자.
public interface CoroutineScope {
    /**
     * Context of this scope.
     */
    public val coroutineContext: CoroutineContext
}

이 인터페이스는 서면으로 이해하는 것이 협정의 작용 범위인데 왜 작용 범위가 있어야 합니까?
Coroutine은 경량급 라인으로 시스템 자원을 소모하지 않는다는 것을 의미하지 않는다.비동기 조작이 비교적 시간이 걸리거나, 비동기 조작에 오류가 발생할 때, 이 Coroutine을 취소해서 시스템 자원을 방출해야 한다.안드로이드 환경에서 일반적으로 모든 인터페이스(Activity, Fragment 등)에서 시작되는 Coroutine은 이 인터페이스에서만 의미가 있다. 만약 사용자가 Coroutine의 실행을 기다릴 때 이 인터페이스를 종료한다면 이 Coroutine를 계속 실행하는 것은 필요없을 수도 있다.또한Coroutine도 적당한 context에서 실행해야 합니다. 그렇지 않으면 오류가 발생할 수 있습니다. 예를 들어 비 UI 라인에서View에 접근하는 것입니다.그래서 Coroutine은 디자인을 할 때 한 범위(Scope) 내에서 실행해야 한다. 이렇게 하면 이 Scope가 취소될 때 안에 있는 모든 하위 Coroutine도 자동으로 취소된다.따라서 Coroutine을 사용하려면 해당 Coroutine Scope를 만들어야 합니다.
그래서 Coroutine Scope는 새로운 Coroutine의 실행 Scope를 정의했을 뿐입니다.모든coroutinebuilder는CoroutineScope의 확장 함수이며 현재 Scope의coroutineContext와 취소 작업을 자동으로 계승합니다.
모든 coroutine builder와 scope 함수 (withContext,coroutine Scope 등) 는 이 함수에 제공된 코드 블록을 실행하기 위해 자신의 Scope와 자신이 관리하는 Job을 사용합니다.또한 이 코드 블록의 모든 하위 Coroutine이 실행되기를 기다릴 것입니다. 모든 하위 Coroutine이 실행되고 돌아올 때 이 코드 블록이 실행됩니다. 이런 행위를 'structured concurrency' (구조화 병발) 라고 부릅니다.
coroutineScope 함수는 또 어떻게 되는 건가요?
공식 문서에서 이해하기 어려운 말을 했다.
서로 다른 구축기에서 협정 역할 영역을 제공하는 것 이외에coroutineScope 구축기를 사용하여 자신의 역할 영역을 설명할 수 있습니다.협업 도메인이 생성되고 서브 협업이 시작되기 전까지는 종료되지 않습니다.runBlocking과coroutineScope의 주요 차이점은 후자가 모든 하위 프로토콜이 실행되기를 기다릴 때 현재 라인을 막지 않는다는 것이다.
우리는 이미 runBlocking 방법이 새로운 협정을 만들 수 있다는 것을 알고 있다. coroutineScope 함수는 보기에 효과가 runBlocking 효과와 매우 비슷하다.그러나 사실 그들 둘은 본질적인 차이가 있다.
앞에서 말했듯이runBlocking은 브리지 인터럽트 코드와 인터럽트 코드를 연결하기 전의 교량으로 그 함수 자체는 인터럽트이지만 그 내부에서suspend 장식된 인터럽트 함수를 실행할 수 있다.내부의 모든 하위 협정의 운행이 끝나기 전에 그는 노선을 막았다.
coroutineScope 함수는 다릅니다.
public suspend fun  coroutineScope(block: suspend CoroutineScope.() -> R): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }

이 함수는suspend에 의해 수식되어 있으며, 마운트 함수입니다. 앞에서 말했듯이 마운트 함수는 라인을 막지 않습니다. 이 함수는 협정만 끊을 뿐 라인을 막지 않습니다.
어떻게 우리의 프로젝트에서 협정을 사용합니까?
MVVM을 사용한다면 androidx.lifecycle:lifecycle-viewmodel-ktx이라는 패키지를 도입하면 View Model에서 viewModelScope이라는 확장 필드를 직접 사용해 협업 여행을 시작할 수 있다.
만약 우리가 MVVM을 사용하지 않았다면,Activity에서 사용하려면 어떻게 해야 합니까?
참조 코드는 다음과 같습니다.
class ScopedActivity : Activity(), CoroutineScope {
    lateinit var job: Job
    // CoroutineScope    
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }
 
    override fun onDestroy() {
        super.onDestroy()
        //   Activity          Scope     job。
        //      Scope       Coroutine         。
        job.cancel()
    }
 
    /*
     *    coroutine builder   scope,    activity               Coroutine
     *      ,     Coroutines        。        。
     */
    fun loadDataFromUI() = launch { // 
  • 은 Activity로 하여금 CoroutineScope 인터페이스를 실현하게 한다.
  • coroutineContext의 get () 방법을 다시 쓰기;
  • onDestroy 방법에서job를 호출합니다.cancel();
  • 은 적당한 위치에서 launch 방법을 사용하면 된다.

  • 어떻게 협정 과정 중 서로 다른 노선을 전환합니까
    안드로이드에서는 메인 라인에서 네트워크 요청을 할 수 없습니다. 저희 협업은 현재 라인에 하숙되어 있기 때문에 협업에서도 네트워크 요청을 할 수 없습니다. 그렇지 않으면 Network On Main Thread Exception 이상으로 보고됩니다.
    이때 우리는 협정에서 라인을 전환하는 데 사용해야 한다. 위 코드에서 어떻게 조작하는지 보여주었다.
    		val ioData = async(Dispatchers.IO) { // 
    async 함수에 Dispatchers.IO 매개 변수를 추가합니다. 이 함수는 launch 함수와 다르게 launch 함수의 반환값은 Job이고 async의 반환값은 사용자가 직접 설정한 Deferred입니다. 비동기적인 결과를 얻을 때 await의 편지를 호출하여 얻을 수 있습니다.
    만약 우리가 시간이 많이 걸리는 조작이 여러 개 있다면?여러 개의 async 함수 호출이 함께 진행됩니까?답은 정해지지 않았다. 그들은 순서대로 집행할 것이다. 즉, asyncawait을 협조하면 현재의 협정이 막힐 것이다.
    그럼 만약에 저희가 여러 조작을 병행해야 한다면요?간단합니다. 그 외층에 launch을 추가하면 됩니다. 이렇게 하면 서로 다른 소모 시간 조작은 서로 다른 협정에서 실행되고 서로 막히지 않아 병행을 실현할 수 있습니다. 코드는 다음과 같습니다.
       fun delayLoad(v: View) = launch {
          launch {
              Logger.d("      1 ${Thread.currentThread().name}")
              val response1 = async(Dispatchers.IO) {
                  Logger.d("${Thread.currentThread().name}")
                  ServiceCreator.create(PlaceService::class.java).login().execute() }
              tv_detail.text = response1.await().body().toString()
          }
           Logger.d("      2 ${Thread.currentThread().name}")
           val response2 = async(Dispatchers.IO) {
               Logger.d("${Thread.currentThread().name}")
               ServiceCreator.create(PlaceService::class.java).login().execute() }
           tv_detail2.text = response2.await().body().toString()
       }
    

    Dispatchers.IO는 또 뭐야?
    먼저 결론을 말하자면 이것은 추상류인CoroutineDispatcher의 실현이고 CoroutineContext 인터페이스의 실현이다.그럼 무엇이 CoroutineContext입니까?
    Persistent context for the coroutine. It is an indexed set of [Element] instances. An indexed set is a mix between a set and a map. Every element in this set has a unique [Key]. Keys are compared by reference. *협정의 지속적인 상하문.색인 세트의 [요소] 인스턴스입니다.*색인 세트는 세트와 매핑의 혼합입니다.*이 집합 중의 모든 원소는 유일한 [키]가 있다.참조 비교 키를 사용합니다.
    그래서 저희가 디스패치에 들어오면입출력 시, 이 새 협정은 디스패치에 생성됩니다.IO에 대응하는 상하문에서 현재 메인 라인이 아니기 때문에 Network OnMainThreadException이 발생하지 않습니다.
    Coroutine Dispatcher는 Coroutine이 실행하는 스레드를 정의합니다.Coroutine Dispatcher는 Coroutine이 특정한 라인에서 실행하는 것을 제한할 수도 있고, 하나의 라인 풀에 분배해서 실행할 수도 있으며, 실행하는 라인을 제한하지 않을 수도 있다.
    Dispatchers에서는 다음과 같은 네 가지 일반적인 구현이 있습니다.
  • Default
  • Main
  • Unconfined(일반적으로 Unconfined를 사용하지 않음)
  • IO

  • 하위 프로토콜은 부모 프로토콜의context를 통합하기 때문에 프로토콜이 있는 라인을 전환하지 않아도 부모 클래스에Dispatcher를 설정할 수 있습니다

    좋은 웹페이지 즐겨찾기