Day 15:完了,我的Coroutine漏出去了.Coroutine的Leak与结构化

Keyword:Coroutine Leak,Structured Concurrency


Memory Leak

在刚入行工作的时候,有经验的前辈常常提醒要注意Memory Leak,如果有发生Memory Leak的问题,就可能逐渐让App卡顿,严重时还会闪退.导致客户解除安装,降低留存率.

虽然这麽说,但是Memory Leak很不容易在开发时发现,由於对功能没有显着的影响,有的时候连测试环节也没能抓到问题,最後偷渡到架上版.这个问题只能靠着Code Review进行预防,但是难免有漏网之鱼.

Memory Leak的原因

最常看见的说法就是由於有匿名物件在内,这个匿名物件会有外部的物件隐性引用,导致外部的物件无法回收.

但是我们常常使用匿名物件,例如点击事件的监听,甚至DataBinding,ViewBinding等等的功能也常常用到匿名物件,这些物件为什麽不会导致Memory Leak? 同样是匿名物件啊.

原来,导致Leak的不是匿名,而是使用到了Thread,仍然活着的Thread在JVM中是属於Garbage Collector Root,而Garbage Collector Root 引用到的所有物件在GC的时候都不会被回收,最後就发生了Memory Leak.

这点在App上发生的更为频繁,因为App发生事件的时机点是不可控的,也许使用者刚按下"读取历史资料"的功能,这功能是耗时工作,因此App生成了一个新Thread来执行.然而使用者这时突然按下了返回离开了页面,如果没有做好预防,这边的刚建立的Thread就会发生Memory Leak.

寻求解决之道

资深的工程师,可以透过经验在开发时避免这些问题,最常见的做法是在物件关闭的发生时,将所有使用到的Thread一并关闭.

但是当团队有新成员加入,或是是刚入行的菜鸟时,就非常有可能忘记这件事,因为关闭Thread这件事是超出使用的物件范围,而是由外部控制.

而由於Kotllin的Coroutine目前也是藉由JVM的Thread来实作,并不是真正意义上的Coroutine环境,所以这些Thread发生的问题,如果使用不当,仍然存在.

官方考虑到了这点,所以限制了Coroutine在使用时,必须要在一个CoroutineScope中,在CoroutineScope结束的时候,其中所有正在执行的Coroutine也会一并被停止,因此便不会发生Coroutine Memory Leak的问题.

回来看看我们前几天写的Android Coroutine内容.viewModelScope限制了这个Coroutine只能在这个ViewModel的生命周期中执行,当ViewModel的生命周期结束後,这个Scope的内容也会停止

fun fetchCafeData(city: String = "") {
        viewModelScope.launch() {
            val result = async { dataRepository.fetchCafesFromNetwork(city) }
            cafeList.value = result.await()
        }
    }

和一般的Thread写法比较一下

...
fun fetchCafeData(city: String = "") {
   thread{
				val result = dataRepository.fetchCafesFromNetwork(city) 
		}
}
...
override fun onCleared() {
        super.onCleared()
				stopAllThread()
    }

可以发现Coroutine执行的生命周期范围,就绑定在使用的区块上面一行,并且不提供生命周期范围即不给使用,这大大降低了忘记填写释放时机的可能性.

结构化

这样的设计方式,Kotlin官方称之为Structured Concurrency,结构化并行

(以下三张图出自)

GitHub - Zewo/Venice: Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

在没有结构化并行的环境,只要一个不小心,任何一个Function内部的Thread都有可能,并且有能力可以超脱整个Function的,影响到整个环境中.这件事非常诡异,在一个Function内建立的物件可以Leak到整个Global环境中,造成管理上的麻烦.

https://github.com/officeyuli/itHome2021/raw/main/day15/687474703a2f2f6c696264696c6c2e6f72672f696e646578312e6a706567.jpeg

但有了结构化并行,就能够在使用时限制Thread的作用域,进而降低Leak的风险

https://github.com/officeyuli/itHome2021/raw/main/day15/687474703a2f2f6c696264696c6c2e6f72672f696e646578322e6a706567.jpeg

Structured Concurrency还能在多个子代Thread适用同样的规则,一并管理这些Thread,同样的,当父辈的Thread结束时,所有子代的Thread也会一并结束.最後会形成一个美丽的Thread树.

https://github.com/officeyuli/itHome2021/raw/main/day15/687474703a2f2f6c696264696c6c2e6f72672f696e646578332e6a706567.jpeg

在Android之中,有官方提供的viewModelScope,lifecycleScope等等来协助管理,但在KMM的iOS专案之中,就没这麽容易了,我们明天来额外做些小手脚.


<<:  制卡机故障後的通报与应变程序

>>:  [DAY 7] _GPIO口的八种模式

Day 28:合并排序法 Merge Sort

Merge Sort采用Divide and Conquer的方式,其实他的概念本身就是递回(rec...

30天轻松学会unity自制游戏-输出手机档案

可以测试一下游戏基本功能可以玩了之後就可以准备输出到手机上测试罗~第一步先到File->Bui...

新增装备 - VSCode 套件介绍

前情提要 身後传来了声音:「哈罗,我叫艾草,是你的入门引导学姊。」 我回头一看却没看到人。 「这里!...

DAY 18 『 画面间跳页传值 - Protocol And Delegate 』

昨天介绍完如何跳页,今天将会分享如何跳页传值。 成品: 刚执行模拟器的样子 按下 Button 後会...

那些被忽略但很好用的 Web API / MutationObserver

我的改变,你看得见! 在开发网页过程中,我们最常做的事情就是对资料进行修改後运用在 DOM 元素上...