看了很多博客,也看了些在线课堂的课程。大神们说的协程 Coroutine 的概念,一直含糊不清。今天自己动手做做实验理解下。
1. 代码如下
private const val i1 = 1000000000
class MainViewModel : ViewModel() {
companion object {
private const val TAG = "MainViewModel"
}
suspend fun getData() {
Trace.beginSection("getData");
Log.e(TAG, "getData before " + Thread.currentThread().name)
viewModelScope.launch(Dispatchers.IO) {
Trace.beginSection("DispatchersIO");
Log.e(TAG, "getData IO 1 " + Thread.currentThread().name)
Thread.sleep(1000)
Log.e(TAG, "getData IO 2 " + Thread.currentThread().name)
Trace.endSection();
}
viewModelScope.launch(Dispatchers.Default) {
Trace.beginSection("DispatchersDefault");
Log.e(TAG, "getData Default 1 " + Thread.currentThread().name)
Thread.sleep(1000)
Log.e(TAG, "getData Default 2 " + Thread.currentThread().name)
Trace.endSection();
}
viewModelScope.launch(Dispatchers.Unconfined) {
Trace.beginSection("DispatchersUnconfined");
Log.e(TAG, "getData Unconfined 1 " + Thread.currentThread().name)
dealJob()
Trace.beginSection("DispatchersUnconfined-");
delay(1000)
Thread.sleep(1000 * 5)
Trace.endSection();
Log.e(TAG, "getData Unconfined 2 " + Thread.currentThread().name)
Trace.endSection();
}
viewModelScope.launch(Dispatchers.Main) {
Log.e(TAG, "getData Main 1 " + Thread.currentThread().name)
Trace.beginSection("DispatchersMain");
dealJob()
Trace.beginSection("DispatchersMain-");
Thread.sleep(1000)
Log.e(TAG, "getData Main 2 " + Thread.currentThread().name)
Trace.endSection();
Trace.endSection();
}
withContext(Dispatchers.IO) {
Trace.beginSection("withContext");
Thread.sleep(1000 * 20)
Trace.endSection();
}
Trace.beginSection("lastLog");
dealJob()
Trace.endSection();
Trace.endSection();
Log.e(TAG, "getData end " + Thread.currentThread().name)
}
private fun dealJob() {
var rr = 0L;
for (i in 0..i1) {
rr += (i % 10)
}
}
}
2. 通过代码 system trace 文件,观察线程切换
见资源:ltrace.html
3. 输出 log
10-11 20:41:04.801 21917 26304 E MainViewModel: getData before DefaultDispatcher-worker-1
10-11 20:41:04.802 21917 26306 E MainViewModel: getData IO 1 DefaultDispatcher-worker-3
10-11 20:41:04.802 21917 26305 E MainViewModel: getData Default 1 DefaultDispatcher-worker-2
10-11 20:41:04.803 21917 26304 E MainViewModel: getData Unconfined 1 DefaultDispatcher-worker-1
10-11 20:41:05.803 21917 26306 E MainViewModel: getData IO 2 DefaultDispatcher-worker-3
10-11 20:41:05.803 21917 26305 E MainViewModel: getData Default 2 DefaultDispatcher-worker-2
10-11 20:41:11.123 21917 26304 E MainViewModel: getData Unconfined 2 DefaultDispatcher-worker-1
10-11 20:41:11.125 21917 21917 E MainViewModel: getData Main 1 main
10-11 20:41:12.633 21917 26197 I Quality : stackInfo :----- pid 21917 at 2023-10-11 20:41:12.626 -----;Cmd line: com.haier.uhome.coroutine;"main" prio=5 tid=2 TIMED_WAITING sysTid=21917; at java.lang.Thread.sleep(Native Method); at java.lang.Thread.sleep(Thread.java:450); at java.lang.Thread.sleep(Thread.java:355); at com.haier.uhome.coroutine.ui.main.MainViewModel$getData$5.invokeSuspend(MainViewModel.kt:50); at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33); at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106); at android.os.Handler.handleCallback(Handler.java:942); at android.os.Handler.dispatchMessage(Handler.java:99); at android.os.Looper.loopOnce(Looper.java:240); at android.os.Looper.loop(Looper.java:351); at android.app.ActivityThread.main(ActivityThread.java:8427); at java.lang.reflect.Method.invoke(Native Method); at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584); at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1013);
10-11 20:41:13.451 21917 21917 E MainViewModel: getData Main 2 main
10-11 20:41:32.439 21917 26304 E MainViewModel: getData end DefaultDispatcher-worker-1
4. trace 部分截图
问:协程到底是什么?
答:协程就是对线程的一次封装。协程把线程的切换逻辑封装到 suspend 下的 launch 方法里。以顺序的代码流程执行线程的切换操作。如以上 log 里第 2 行、第 5 行,IO 相关的执行完再执行第 8 行的 Main 线程的逻辑。符合人的思维逻辑。
问:协程中非主线程的工作,会创建线程吗?
答:就像上边 log 和 trace 文件所示。协程并不是完全不做线程创建和切换,子线程 26304,26305,26848 等子线程依然是创建了的。
问:使用协程会提升性能吗?
答:从红框里可以看到,代码 26 行到 34 行,和 46 行到 50 行内容在同一个线程执行。说明协程对线程进行了有效的复用。减少线程的创建和销毁,当然能提升性能
问:在执行协程内部非主线程的内容时,主线程在干什么?是如果实现主线程不耗时等待导致 anr 的?
答:从主线程的状态可以看到,执行非主线程的内容时,主线程在 sleep,释放了 cpu 才不会有主线程的 anr 计时,也就不会导致 anr。
见资源:trace.html
如图所示,子线程 30960 会在合适的位置唤醒包括主线程在内的其他线程,达到了线程切换,顺序执行,又不会 block 导致 anr 的目的。
5. 团队介绍
「三翼鸟数字化技术平台-场景设计交互平台」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。