协程 Coroutine 到底是个啥?

avatar
@海尔优家智能科技(北京)有限公司

看了很多博客,也看了些在线课堂的课程。大神们说的协程 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 文件,观察线程切换

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。

如图所示,子线程 30960 会在合适的位置唤醒包括主线程在内的其他线程,达到了线程切换,顺序执行,又不会 block 导致 anr 的目的。

5. 团队介绍

「三翼鸟数字化技术平台-场景设计交互平台」 主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。