协程.md

作者: just0119 | 来源:发表于2021-08-11 22:22 被阅读0次
    • 协程
      • 协程中使用suspend修饰方法,代表该方法可在协程中挂起。但并不是协程方法必须使用suspend修饰
      • 协程和线程的对比:
        • 线程拥有独立的栈、局部变量,给予进程的共享内存,多线程需要加锁来控制访问,而加锁会容易导致死锁,线程的调度有内核控制(时间片竞争机制)程序员无法介入,线程切换的代价较大:
          1、线程对象的创建和初始化及销毁
          2、线程上下文切换
          3、线程状态的切换由系统内核完成
          4、对变量操作需要加锁
        • Coroutine协程运行在线程中,有自己的栈内存和局部变量,共享成员变量,Coroutine可以直接编辑方法,由程序员实现切换和调度,不采用时间片竞争机制,一个线程可以跑多个协程,一个时间只能执行一个协程
          1、因为在一个线程中,协程的切换不涉及线程上下文的切换,不存在数据资源并发,不用加锁,执行效率高
          2、协程是非阻塞的,一个协程在进入阻塞后不会阻塞线程,线程会执行其他协程
      • 协程的使用
        • GlobalScope.launch()
    private fun getTest() {
        Log.d("cjq", "协程初始化开始,时间: " + System.currentTimeMillis())
        GlobalScope.launch(Dispatchers.Unconfined) {
            Log.d("cjq","协程 ${Thread.currentThread()}")
            for (i in 1..3) {
                Log.d("cjq", "协程任务1打印第$i 次,时间: " + System.currentTimeMillis())
            }
            delay(500)
            for (i in 1..3) {
                Log.d("cjq", "协程任务2打印第$i 次,时间: " + System.currentTimeMillis())
            }
        }
        Log.d("cjq","主线程 ${Thread.currentThread()}")
        Thread.sleep(500)
        Log.d("cjq", "主线程运行,时间: " + System.currentTimeMillis())
    
        for (i in 1..3) {
            Log.d("cjq", "主线程打印第$i 次,时间: " + System.currentTimeMillis())
        }
    }
    D/cjq: 协程初始化开始,时间: 1626134403936
    D/cjq: 主线程 Thread[main,5,main]
    D/cjq: 协程 Thread[DefaultDispatcher-worker-1,5,main]
    D/cjq: 协程任务1打印第1 次,时间: 1626134404003
    D/cjq: 协程任务1打印第2 次,时间: 1626134404003
    D/cjq: 协程任务1打印第3 次,时间: 1626134404003
    D/cjq: 主线程运行,时间: 1626134404539
    D/cjq: 主线程打印第1 次,时间: 1626134404540
    D/cjq: 主线程打印第2 次,时间: 1626134404540
    D/cjq: 主线程打印第3 次,时间: 1626134404540
    D/cjq: 协程任务2打印第1 次,时间: 1626134404552
    D/cjq: 协程任务2打印第2 次,时间: 1626134404552
    D/cjq: 协程任务2打印第3 次,时间: 1626134404552
    
    public fun CoroutineScope.launch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
    ): Job {
        val newContext = newCoroutineContext(context)
        val coroutine = if (start.isLazy)
            LazyStandaloneCoroutine(newContext, block) else
            StandaloneCoroutine(newContext, active = true)
        coroutine.start(start, coroutine, block)
        return coroutine
    }
    

    launcher有3个参数和一个返回值job ,第一个参数CoroutineContext 理解为协程的上下文,可设置4种线程模式:
    ~ Dispatchers.Default 不设置即是Dispatcher.Default
    ~ Dispatcher.IO
    ~ Dispatcher.Main
    ~ Dispatcher.Umconfined 未指定线程,在当前线程
    第二个参数CoroutineStart 启动模式
    ~ Default 默认模式,创建就启动
    ~ Atomic
    ~ Undispatched
    ~ Lazy 懒加载模式,需要时启动

    private fun getTest() {
        Log.d("cjq", "协程初始化开始,时间: " + System.currentTimeMillis())
        val job :Job = GlobalScope.launch(start = CoroutineStart.LAZY) {
            Log.d("cjq","协程 ${Thread.currentThread()}")
            for (i in 1..3) {
                Log.d("cjq", "协程任务1打印第$i 次,时间: " + System.currentTimeMillis())
            }
            delay(500)
            for (i in 1..3) {
                Log.d("cjq", "协程任务2打印第$i 次,时间: " + System.currentTimeMillis())
            }
        }
        Log.d("cjq","主线程 ${Thread.currentThread()}")
        Thread.sleep(500)
        Log.d("cjq", "主线程运行,时间: " + System.currentTimeMillis())
        job.start()
        for (i in 1..3) {
            Log.d("cjq", "主线程打印第$i 次,时间: " + System.currentTimeMillis())
        }
    }
    D/cjq: 协程初始化开始,时间: 1626135267624
    D/cjq: 主线程 Thread[main,5,main]
    D/cjq: 主线程运行,时间: 1626135268190
    D/cjq: 主线程打印第1 次,时间: 1626135268216
    D/cjq: 主线程打印第2 次,时间: 1626135268216
    D/cjq: 主线程打印第3 次,时间: 1626135268217
    D/cjq: 协程 Thread[DefaultDispatcher-worker-1,5,main]
    D/cjq: 协程任务1打印第1 次,时间: 1626135268218
    D/cjq: 协程任务1打印第2 次,时间: 1626135268218
    D/cjq: 协程任务1打印第3 次,时间: 1626135268218
    D/cjq: 协程任务2打印第1 次,时间: 1626135268760
    D/cjq: 协程任务2打印第2 次,时间: 1626135268760
    D/cjq: 协程任务2打印第3 次,时间: 1626135268760
    

    返回值job 理解为协程对象本身
    1、 job.start() 启动协程,除了lazy模式,其他都不需要手动启动
    2、 job.join() 等待协程执行完毕
    3、 job.cancel() 取消协程
    4、 job.cancelAndJoin() 等待协程执行完毕后取消

    • GlobalScope.async
    private fun getTest() {
        Log.d("cjq", "协程初始化开始,时间: " + System.currentTimeMillis())
        val job :Job = GlobalScope.launch() {
            Log.d("cjq","协程 ${Thread.currentThread()}")
            val deferred = GlobalScope.async {
                delay(1000)
                Log.d("cjq","协程async ${Thread.currentThread()}")
                return@async "async"
            }
            Log.d("cjq","协程async外 ${Thread.currentThread()}")
           val result = deferred.await()
            Log.d("cjq","协程async返回值 $result")
        }
        Log.d("cjq","主线程 ${Thread.currentThread()}")
    }
     D/cjq: 协程初始化开始,时间: 1626136029482
     D/cjq: 主线程 Thread[main,5,main]
     D/cjq: 协程 Thread[DefaultDispatcher-worker-1,5,main]
     D/cjq: 协程async外 Thread[DefaultDispatcher-worker-1,5,main]
     D/cjq: 协程async Thread[DefaultDispatcher-worker-1,5,main]
     D/cjq: 协程async返回值 async
    

    deferred继承Job,并增加了一个方法await,async不会阻塞当前线程,但会阻塞所在协程,也就是挂起。如下比较:

    private fun test2() {
        GlobalScope.launch(Dispatchers.IO) {
            Log.d("cjq", "launch : ${Thread.currentThread()}")
            val token = GlobalScope.async {
               return@async getToken()
            }.await()
            Log.d("cjq","token:$token")
            val string = GlobalScope.async {
                return@async getString()
            }.await()
            getBody()
    
        }
        Log.d("cjq", "out : ${Thread.currentThread()}")
    }
    suspend fun getToken():String{
        Log.d("cjq", "gettokenstart : ${Thread.currentThread()}")
        delay(300)
        Log.d("cjq", "gettoken : ${Thread.currentThread()}")
        return "sdf"
    }
    suspend fun getString():String{
        Log.d("cjq", "getStringstart : ${Thread.currentThread()}")
        delay(100)
        Log.d("cjq", "getString : ${Thread.currentThread()}")
        return "sdf"
    }
    fun getBody():String{
        Log.d("cjq", "getBody : ${Thread.currentThread()}")
        return "body"
    }
    
    D/cjq: out : Thread[main,5,main]
    D/cjq: launch : Thread[DefaultDispatcher-worker-1,5,main]
    D/cjq: gettokenstart : Thread[DefaultDispatcher-worker-3,5,main]
    D/cjq: gettoken : Thread[DefaultDispatcher-worker-3,5,main]
    D/cjq: token:sdf
    D/cjq: getStringstart : Thread[DefaultDispatcher-worker-2,5,main]
    D/cjq: getString : Thread[DefaultDispatcher-worker-2,5,main]
    D/cjq: getBody : Thread[DefaultDispatcher-worker-1,5,main]
    
    private fun test2() {
        GlobalScope.launch(Dispatchers.IO) {
            Log.d("cjq", "launch : ${Thread.currentThread()}")
            val token = GlobalScope.async {
               return@async getToken()
            }
            Log.d("cjq","token:$token")
            val string = GlobalScope.async {
                return@async getString()
            }
            getBody()
    
        }
        Log.d("cjq", "out : ${Thread.currentThread()}")
    }
    suspend fun getToken():String{
        Log.d("cjq", "gettokenstart : ${Thread.currentThread()}")
        delay(300)
        Log.d("cjq", "gettoken : ${Thread.currentThread()}")
        return "sdf"
    }
    suspend fun getString():String{
        Log.d("cjq", "getStringstart : ${Thread.currentThread()}")
        delay(100)
        Log.d("cjq", "getString : ${Thread.currentThread()}")
        return "sdf"
    }
    fun getBody():String{
        Log.d("cjq", "getBody : ${Thread.currentThread()}")
        return "body"
    }
    D/cjq: out : Thread[main,5,main]
    D/cjq: launch : Thread[DefaultDispatcher-worker-1,5,main]
    D/cjq: token:DeferredCoroutine{Active}@d6f7b86
    D/cjq: gettokenstart : Thread[DefaultDispatcher-worker-3,5,main]
    D/cjq: getBody : Thread[DefaultDispatcher-worker-1,5,main]
    D/cjq: getStringstart : Thread[DefaultDispatcher-worker-2,5,main]
    D/cjq: getString : Thread[DefaultDispatcher-worker-3,5,main]
    D/cjq: gettoken : Thread[DefaultDispatcher-worker-3,5,main]
    
    • runblocking
      runblocking和launch区别在于runblocking的delay方法会阻塞当前线程,和Thread.sleep()一样
    private fun test2(){
        runBlocking {
            Log.d("cjq", "run:${Thread.currentThread().name}")
            delay(1000)
            Log.d("cjq","runblocking")
        }
        Log.d("cjq", "test:${Thread.currentThread().name}")
        Thread.sleep(2000)
        Log.d("cjq","sleep")
    }
     D/cjq: run:main
     D/cjq: runblocking
     D/cjq: test:main
     D/cjq: sleep
    
    • 协程的挂起和恢复
      1、协程内按顺序执行
      2、协程挂起后需要等待挂起完成,且线程空闲才继续执行
      3、suspend修饰的方法,挂起的是协程本身,不仅是该方法
      单协程多suspend,如下:
    private fun test2() {
        GlobalScope.launch(Dispatchers.Main) {
            Log.d("cjq", "launch : ${Thread.currentThread()}")
            getToken()
            getString()
            getBody()
        }
        Log.d("cjq", "out : ${Thread.currentThread()}")
    }
    suspend fun getToken():String{
        delay(300)
        Log.d("cjq", "gettoken : ${Thread.currentThread()}")
        return "sdf"
    }
    suspend fun getString():String{
        delay(100)
        Log.d("cjq", "getString : ${Thread.currentThread()}")
        return "sdf"
    }
    fun getBody():String{
        Log.d("cjq", "getBody : ${Thread.currentThread()}")
        return "body"
    }
     D/cjq: out : Thread[main,5,main]
     D/cjq: launch : Thread[main,5,main]
     D/cjq: gettoken : Thread[main,5,main]
     D/cjq: getString : Thread[main,5,main]
     D/cjq: getBody : Thread[main,5,main]  
    

    多协程多suspend

    private fun test2() {
        GlobalScope.launch(Dispatchers.Main) {
            Log.d("cjq", "launch : ${Thread.currentThread()}")
            val token = GlobalScope.launch {
               getToken()
            }
            val string = GlobalScope.launch {
                getString()
            }
            getBody()
        }
        Log.d("cjq", "out : ${Thread.currentThread()}")
    }
    suspend fun getToken():String{
        delay(300)
        Log.d("cjq", "gettoken : ${Thread.currentThread()}")
        return "sdf"
    }
    suspend fun getString():String{
        delay(100)
        Log.d("cjq", "getString : ${Thread.currentThread()}")
        return "sdf"
    }
    fun getBody():String{
        Log.d("cjq", "getBody : ${Thread.currentThread()}")
        return "body"
    }
    D/cjq: out : Thread[main,5,main]
    D/cjq: launch : Thread[main,5,main]
    D/cjq: getBody : Thread[main,5,main]
    D/cjq: getString : Thread[DefaultDispatcher-worker-2,5,main]
    D/cjq: gettoken : Thread[DefaultDispatcher-worker-2,5,main]
    

    多协程多suspend使用async,可以用来控制协程执行顺序

    private fun test2() {
        GlobalScope.launch(Dispatchers.Main) {
            Log.d("cjq", "launch : ${Thread.currentThread()}")
            val token = GlobalScope.async {
                return@async getToken()
            }.await()
            val string = GlobalScope.async {
                return@async getString()
            }.await()
            getBody()
        }
        Log.d("cjq", "out : ${Thread.currentThread()}")
    }
    suspend fun getToken():String{
        delay(300)
        Log.d("cjq", "gettoken : ${Thread.currentThread()}")
        return "sdf"
    }
    suspend fun getString():String{
        delay(100)
        Log.d("cjq", "getString : ${Thread.currentThread()}")
        return "sdf"
    }
    fun getBody():String{
        Log.d("cjq", "getBody : ${Thread.currentThread()}")
        return "body"
    }
     D/cjq: out : Thread[main,5,main]
     D/cjq: launch : Thread[main,5,main]
     D/cjq: gettoken : Thread[DefaultDispatcher-worker-2,5,main]
     D/cjq: getString : Thread[DefaultDispatcher-worker-2,5,main]
     D/cjq: getBody : Thread[main,5,main]
    

    协程挂起后恢复所处线程:哪个线程恢复的协程,协程运行在哪个线程

    private fun test2() {
        GlobalScope.launch(Dispatchers.IO) {
            Log.d("cjq", "launch : ${Thread.currentThread()}")
            val token = GlobalScope.async {
               return@async getToken()
            }.await()
            Log.d("cjq","token:$token")
            val string = GlobalScope.async {
                return@async getString()
            }.await()
            getBody()
    
        }
        Log.d("cjq", "out : ${Thread.currentThread()}")
    }
    suspend fun getToken():String{
        Log.d("cjq", "gettokenstart : ${Thread.currentThread()}")
        delay(300)
        Log.d("cjq", "gettoken : ${Thread.currentThread()}")
        return "sdf"
    }
    suspend fun getString():String{
        Log.d("cjq", "getStringstart : ${Thread.currentThread()}")
        delay(100)
        Log.d("cjq", "getString : ${Thread.currentThread()}")
        return "sdf"
    }
    fun getBody():String{
        Log.d("cjq", "getBody : ${Thread.currentThread()}")
        return "body"
    }
    
    D/cjq: out : Thread[main,5,main]
    D/cjq: launch : Thread[DefaultDispatcher-worker-1,5,main]
    D/cjq: gettokenstart : Thread[DefaultDispatcher-worker-3,5,main]
    D/cjq: gettoken : Thread[DefaultDispatcher-worker-3,5,main]
    D/cjq: token:sdf
    D/cjq: getStringstart : Thread[DefaultDispatcher-worker-2,5,main]
    D/cjq: getString : Thread[DefaultDispatcher-worker-2,5,main]
    D/cjq: getBody : Thread[DefaultDispatcher-worker-1,5,main]
    
    • suspendCancellableCoroutine和suspendCoroutine
      将回调函数转换为协程,suspendCancellableCoroutine返回CancellableContinuation,用resume和resumeWithException 处理回调和抛出异常,和suspendCoroutine的区别在于suspendCancellableCoroutine可以通过cancel()方法手动取消协程执行

    • lifecycleScope、 viewmodelScope
      引入方式:

    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'//lifecycleScope
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'//viewModelScope
    
    class MainFragment : Fragment(){
        val model:RvViewModel by viewModels()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            lifecycleScope.launch {  }
        }
    }
    
    class RvViewModel : ViewModel() {
        fun getData() {
            viewModelScope.launch(Dispatchers.IO) { 
            }
        }
    }
    

    1、GlobalScope的生命周期是process级别的,即使activity或fragment销毁,协程仍在执行
    2、lifecycleScope的作用域CoroutineScope绑定到lifecycleowner的生命周期,取消此作用域,协程销毁,lifecycleowner生命周期可以绑定到activity中,所以lifecycleScopr间接绑定到Activity和Fragment的生命周期。只能用于activity或fragment
    3、viewmodelScope的作用域CoroutineScope的作用域绑定到viewmodel,viewmodel销毁,作用域清除,只能用于viewmodel

    • kotlin.runcathing
      代码块内会进行try catch处理,实现了onSuccess和onFailure方法
        private fun initTest() {
            GlobalScope.launch {
                kotlin.runCatching {
                    var j: Int = updateUser()
                    Log.d("cjq-1", "$j")
                    return@runCatching j
                }.onSuccess {
                    Log.d("cjq-2", "$it")
                }.onFailure {
                    Log.d("cjq-3", "${it.message}")
                }
            }
        }
    
        private fun updateUser(): Int {
    //        return 2
            throw Exception("sdf")
        }
        D/cjq-3: sdf
    
        private fun initTest() {
            GlobalScope.launch {
                kotlin.runCatching {
                    var j: Int = updateUser()
                    Log.d("cjq-1", "$j")
                    return@runCatching j
                }.onSuccess {
                    Log.d("cjq-2", "$it")
                }.onFailure {
                    Log.d("cjq-3", "${it.message}")
                }
            }
        }
    
        private fun updateUser(): Int {
            return 2
    //        throw Exception("sdf")
        }
        D/cjq-1: 2
        D/cjq-2: 2
    
    • AtomicBoolean 原子变量,在值变化的时候不允许在之间插入,保持操作的原子性
    private val mPending = AtomicBoolean(false)
    
    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner) { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }
    
    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }
    public final boolean compareAndSet(boolean expect, boolean update) {
        return U.compareAndSwapInt(this, VALUE,
                                   (expect ? 1 : 0),
                                   (update ? 1 : 0));
    }
    

    compareAndSet()方法:
    1、比较mPenging值和expect值是否相等,相等则执行方法
    2、将mPending赋值为update
    以上的方法则为只有在setValue()方法执行后才会执行observer.onChanged(),并执行线程同步操作

    相关文章

      网友评论

          本文标题:协程.md

          本文链接:https://www.haomeiwen.com/subject/pryjpltx.html