iT邦幫忙

2021 iThome 鐵人賽

DAY 4
1
Mobile Development

解鎖kotlin coroutine的各種姿勢-新手篇系列 第 4

day 4 I'm your father, coroutine父子繼承關係

  • 分享至 

  • xImage
  •  

上面講到job會由系統分配,但為什麼我們又能把job當作參數傳入coroutine呢?

繼承

在前面講過,我們可以在coroutine再建立coroutine,並在新的CoroutineScope建立時,依然能透過這些可選的建構子,改變子coroutine的行為,那麼我們沒有聲明的coroutine context element怎麼實現呢?

從parent CoroutineContext,而正式的建構公式是
Parent context = Defaults + inherited CoroutineContext + arguments

啥?我建構的child coroutine會從parent CoroutineContext繼承,然後做出... Parent context?
meme

對,我第一次看到也矇了,讓我現在解釋解釋

coroutine context inherent

我不得已,還是用ppt做了圖出來,這邊轉化成code會是這樣

val scope = CoroutineScope(Job())
scope.launch ( Dispatchers.IO ){

}

聽我娓娓道來,為甚麼會有三個context要看

當你在coroutineScope裡面創建coroutine時,他會先繼承parent coroutineContext的值,再用你傳入的建構參數複寫,這邊是Dispatcher. IO,這時就會出現圖中的Parent context,但這時還沒結束,在parent context裡面的是父coroutine的Job(),而針對我們新建立的coroutine,系統會分配一個新的Job給他

The resulting parent CoroutineContext has Dispatchers.IO instead of the scope’s CoroutineDispatcher since it was overridden by the argument of the coroutine builder.

Also, check that the Job in the parent CoroutineContext is the instance of the scope’s Job (red color), and a new instance of Job (green color) has been assigned to the actual CoroutineContext of the new coroutine.

這關係到coroutine context的繼承,當你launch一個coroutineScope會回傳一個job,在coroutine裡面創建coroutine也會回傳一個Job

val rootJob = CoroutineScope(Job()).launch{
    val job = launch{
    
    }
}

先看一下,創建一個CoroutineScope會回傳了一個scope,launch之後回再回傳一個Job

但此Job非彼Job,可以看看這精美的comment,我直接切重點

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job].

看到重點沒returns a reference of the new coroutine大概就是這樣
回傳的Job是a reference of the new coroutine,傳入的Job是CompletableJob,傳入後他會在CoroutineContext這個indexed set找job,如果沒有,他就自己建立實例

聽我講半天,還是印個log出來給你看

val thirdJob = Job()

viewModelScope.launch{
    val a = launch (thirdJob) {
    }
    Timber.d("third")
    Timber.d(thirdJob.toString())

    Timber.d("a")
    Timber.d(a.toString())
}
D/CoroutineFragment$test: third
D/CoroutineFragment$test: JobImpl{Active}@d02612f
D/CoroutineFragment$test: a
D/CoroutineFragment$test: StandaloneCoroutine{Active}@cf9e33c

既然已經分清楚Job了,還是必須解釋為什麼是這樣設計的?

記得我們說過Job是用來控制和管理coroutine的生命週期、行為等等的吧!如果每次繼承,都用同一個Job,那問題就大了,你完成任務的coroutine不能被取消,佔著記憶體空間不做事,你沒有辦法對各coroutine做出不同行為,如等待結果再執行,等等等等

當我們在一個coroutine裡面再建立coroutine時,會先繼承到parent context的Job,這步驟基本上是再說
coroutine-parent

而新建的coroutine並不會是他的parent coroutine,系統會自動配發新的Job,讓我們有需要時再用變數辨別、操作,極大限度地減少開發者要完成的工作,同時也更加的靈活,符合structured concurrency的設計

繼承的理解盲點

假設這樣一段code,child1的parent是job,而supervisorJob實際上毫無作用,這次因為每個新的協程會被系統分配新的job實例,在這個範例中覆寫了supervisorJob()

val scope = CoroutineScope(Job())
scope.launch(SupervisorJob()) {
    // new coroutine -> can suspend
   launch {
        // Child 1
    }
    launch {
        // Child 2
    }
}


supervisorJob或是supervisorScope只有在創建scope時傳入才有效,下一篇會講講如何正確的用supervisor

官方blog corotuine first thing first


上一篇
day3 讓我看看,什麼是Coroutine Scope
下一篇
day 5 knock, knock我要開始coroutine
系列文
解鎖kotlin coroutine的各種姿勢-新手篇30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
南國安迪
iT邦新手 3 級 ‧ 2021-09-21 12:58:13

i'm your father /images/emoticon/emoticon37.gif

我要留言

立即登入留言