Skip to content
Able Blog
Home
Others
  • Home
      • 学习笔记
          • Android
            • 简历_赵思琦_Android_5年经验
                • Kotlin
                    • 协程
                      • 01. 基础
                        • 1. 协程作用域
                          • 1.1 协程作用域(CoroutineScope)
                          • 2. 协程的取消
                            • 2.1 如何让协程可被取消
                              • 2.2 等待协程的结果
                              • 3. 协程的异常
                                • 3.1 SupervisorJob
                                  • 3.2 launch和async抛出异常
                                    • 3.2.3 async创建的协程作用域
                                      • 3.3 使用CoroutineExceptionHandler捕获,launch创建的,协程中的异常
                                      • 4. 协程种不应被取消的操作

                              01. 基础

                              author iconAblecalendar icon2022年5月29日category icon
                              • Kotlin 协程
                              tag icon
                              • 学习笔记
                              word icon约 1757 字

                              此页内容
                              • 1. 协程作用域
                                • 1.1 协程作用域(CoroutineScope)
                              • 2. 协程的取消
                                • 2.1 如何让协程可被取消
                                • 2.2 等待协程的结果
                              • 3. 协程的异常
                                • 3.1 SupervisorJob
                                • 3.2 launch和async抛出异常
                                • 3.2.3 async创建的协程作用域
                                • 3.3 使用CoroutineExceptionHandler捕获,launch创建的,协程中的异常
                              • 4. 协程种不应被取消的操作

                              # 01. 基础

                              # 1. 协程作用域

                              协程作用域用于控制一个协程的生命周期。

                              # 1.1 协程作用域(CoroutineScope)

                              启动并控制一个协程,都必须创建CoroutineScope对象,并将CoroutineContext作为构造参数。

                              # 1.1.1 协程上下文(CoroutineContext)

                              协程上下文由以下元素定义了协程的行为

                              # Job

                              Job控制协程的生命周期

                              Job的生命周期经历以下一系列状态:新建,活动,正在完成,已完成,正在取消,已取消

                              图片

                              调用方法控制协程:

                              • start() 启动与此Job相关的协程

                              • cancel() 取消该Job相关的协程

                              • join() 暂停当前协程,直到此Job完成

                              或是获取协程当前状态:

                              • children() 返回子Job

                              • isActive() 处于活动状态

                              • isCancelled() 已被取消

                              • isCompleted() 已完成

                              # CoroutineDispatcher

                              将协程分配到指定的线程

                              • Main 用于界面交互和执行快速工作

                              • Default 适合主线程之外执行大量占用CPU资源的工作

                              • IO 适合执行磁盘或网络IO

                              • Unconfined

                              # CoroutineName

                              对应线程的名称

                              # CoroutineExceptionHandler

                              处理未捕获的异常

                                val exceptionHandler: CoroutineExceptionHandler =
                                          CoroutineExceptionHandler { coroutineContext, throwable ->
                                              Log.e("异常抛出",throwable.message.toString())
                                          }
                              CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
                                              launch {
                                                  launch {
                                                      throw Exception("作用域1")
                                                  }
                                              }
                                          }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              # 父级协程上下文与子级协程上下文

                              如: 父级的CoroutineContext = Job()+Dispatcher.Main+CoroutineExceptionHandler实例

                              那么:子级的CoroutineContext 如果有参数Dispatchers.IO,就会覆盖父级对应的参数。

                              (Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)

                              由于我们创建一个新协程总会返回一个新的Job实例,所以,父级与新协程的Job永远不会是同一个实例。

                              # 2. 协程的取消

                              通常我们会开启一个协程做CPU密集型任务,遇到一些情况,需要取消。 但是,在协程中,只有工作完成后才会被移入Cancelled状态。

                              # 2.1 如何让协程可被取消

                              # 2.1.1 挂起函数

                              如delay(),withConext()等

                              # 2.2.2 job.isActive或ensureActive()来检查

                              在工作中判断当前job是否在活动状态,如果不在就不执行 : job.isActive

                              或是使用ensureActive()函数,本质上也是判断是否为活动状态,如果不是,抛出CancellationException 异常

                              # 2.2.3 yieId()

                              它的第一个操作就是判断当前是否为isActive,如果否,抛出CancellationException 异常。

                              # 2.2 等待协程的结果

                              # 2.2.1 Job.join()

                              挂起一个协程直到当前协程完成。

                              # 2.2.2 Deferred.await()

                              协程完成时通过.await()返回结果

                              # 2.2.3 他们前后调用cancel()会发生什么

                              • 先cancel后join 会将当前协程挂起直到Job完成。

                              • 先join后cancel 没效果,因为job已经完成了。

                              • 先cancel后await await不会返回结果,因为该Job已经被取消了,并且会抛出CancellationException异常。

                              • 先await后cancle Job已经完成,cancle自然没效果。

                              # 2.3 不可取消的协程上下文 withContext(NonCancellable)

                              协程被取消时,我们可能需要关闭一些正在使用资源或是有一些记录,清理的代码需要添加。

                              这时候 ,我们需要捕获到cancel的异常然后做处理,并且在finall中关闭资源或是日志等

                                     CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
                                              var nextPrintTime = System.currentTimeMillis()
                                              val scope1 = launch {
                              
                                                  try {
                                                      var count=0
                                                      while (count<5){
                                                          ensureActive()
                                                          if(nextPrintTime<=System.currentTimeMillis()){
                                                              nextPrintTime+=1000
                                                              count++
                                                              Log.i("scope1","第$count 次“)")
                                                          }
                              
                                                      }
                                                  }catch (e:CancellationException){
                                                      LogUtils.i(e.message)
                                                  }finally {
                              
                                                  }
                                              }
                                              delay(1000)
                                              scope1.cancel()
                                          }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              12
                              13
                              14
                              15
                              16
                              17
                              18
                              19
                              20
                              21
                              22
                              23
                              24

                              但是,由于该job已经被取消了,那么在该job中的挂起函数也是无效的,这时,就需要用到withContext(NonCancellable)

                                withContext(NonCancellable){
                                       delay(1000L) // 或者其他挂起函数 
                                       println(“Cleanup done!”)
                                }
                              
                              1
                              2
                              3
                              4

                              # suspendCancellableCoroutine

                              将回调改为协程(改为挂起函数),可使用suspendCoroutine。 但是suspendCancellableCoroutine更好,他可以使用 continuation.invokeOnCancellation来完成取消后的工作。

                              suspend fun work() {
                                 return suspendCancellableCoroutine { continuation ->
                                     continuation.invokeOnCancellation { 
                                        // 可以做一些清理工作
                                     }
                                 // 其余的实现
                              }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7

                              suspendCancellableCoroutine使用示例

                                interface CallBack<T> {
                                      fun onScuccess(t: T)
                                      fun onFailed(e: Exception)
                                  }
                              
                                  fun login(callBack: CallBack<String>){
                                      callBack.onScuccess("登录成功")
                                  }
                              
                                  suspend fun getResult():String = suspendCancellableCoroutine {
                                      login(object :CallBack<String>{
                                          override fun onScuccess(t: String) {
                                              it.resume(t)
                                          }
                              
                                          override fun onFailed(e: Exception) {
                                              it.resumeWithException(e)
                                          }
                              
                                      })
                                  }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              12
                              13
                              14
                              15
                              16
                              17
                              18
                              19
                              20
                              21

                              # 3. 协程的异常

                              协程中,出现异常时,会将它传播到它的父级。 这时, 父级会进行如下三步:

                              1. 取消其余子协程。

                              2. 取消自身。

                              3. 将异常传播给他的父级。

                              所有的异常都必须被处理。

                              # 3.1 SupervisorJob

                              使用SupervisorJob,子协程的异常不会传播。

                              SupervisorJob只在SuperviserScope{}或CoroutineScope(SupervisorJob())创建的作用域下有效。

                              并且,它的CoroutineContext中没有CoroutineExceptionHandler。

                              
                              
                                          CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
                                              launch {
                                                  var i = 0;
                                                  while (i<10) {
                                                      delay(1000)
                                                      LogUtils.i("scope", "第${i++}次")
                                                  }
                                              }
                              
                                              supervisorScope {
                                                  val supervisorJob = SupervisorJob()
                              
                                                  withContext(supervisorJob){
                                                      try {
                                                          var i = 0;
                                                          while (true) {
                                                              if (i == 2) {
                                                                  throw Exception("异常")
                                                              }
                              
                                                              delay(1000)
                                                              LogUtils.i("supervisorScope", "第${i++}次")
                                                          }
                                                      }catch (e:Exception){
                                                          LogUtils.e("supervisorScope","捕获异常,但不传播")
                                                      }
                                                  }
                              
                              
                              
                                              }
                              
                                              val supervisorJob = SupervisorJob()
                                              CoroutineScope(supervisorJob).launch {
                                                  try {
                                                      var i = 0;
                                                      while (true) {
                                                          if (i == 5) {
                                                              throw Exception("异常")
                                                          }
                              
                                                          delay(1000)
                                                          LogUtils.i("supervisorScope2", "第${i++}次")
                                                      }
                                                  }catch (e:Exception){
                                                      LogUtils.e("supervisorScope2","捕获异常,但不传播")
                                                  }
                              
                              
                              
                                              }
                              
                                              val supervisorJob1 = SupervisorJob()
                                              CoroutineScope(supervisorJob1).launch {
                                                  try {
                                                      var i = 0;
                                                      while (true) {
                                                          if (i == 4) {
                                                              throw Exception("异常")
                                                          }
                              
                                                          delay(1000)
                                                          LogUtils.i("supervisorScope3", "第${i++}次")
                                                      }
                                                  }catch (e:Exception){
                                                      LogUtils.e("supervisorScope3","捕获异常,但不传播")
                                                  }
                                              }
                              
                                          }
                              
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              12
                              13
                              14
                              15
                              16
                              17
                              18
                              19
                              20
                              21
                              22
                              23
                              24
                              25
                              26
                              27
                              28
                              29
                              30
                              31
                              32
                              33
                              34
                              35
                              36
                              37
                              38
                              39
                              40
                              41
                              42
                              43
                              44
                              45
                              46
                              47
                              48
                              49
                              50
                              51
                              52
                              53
                              54
                              55
                              56
                              57
                              58
                              59
                              60
                              61
                              62
                              63
                              64
                              65
                              66
                              67
                              68
                              69
                              70
                              71
                              72
                              73

                              如上示例:

                              supervisorScope工作完成后,其他SupervisorJob才开始执行。

                              # 3.2 launch和async抛出异常

                              # 3.2.1 launch创建的协程作用域

                              由launch创建的可使用CoroutineExceptionHandler发送异常消息或try-catch捕获异常。

                              • 通过try-catch捕获异常不会传播。

                              • CoroutineExceptionHandle 异常会传播,影响其他同级作用域与父级。

                              # 3.2.3 async创建的协程作用域

                              未能捕获异常,异常传播:

                                              try {
                                                  async {
                                                      throw Exception()
                                                  }
                                              }catch (e:Exception){
                                                  LogUtils.e("异常捕获")
                                              }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7

                              捕获异常,异常传播:

                                              try {
                                                  async {
                                                      throw Exception()
                                                  }.await()
                                              }catch (e:Exception){
                                                  LogUtils.e("异常捕获")
                                              }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7

                              协程内部捕获,不传播:

                                                  async {
                                                      try {
                                                          throw Exception()
                                                      }catch (e:Exception){
                                                          LogUtils.e("异常捕获")
                                                      }
                                                  }.await()
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7

                              # 3.3 使用CoroutineExceptionHandler捕获,launch创建的,协程中的异常

                              观察异常的传播与捕获

                                      val exceptionHandler: CoroutineExceptionHandler =
                                          CoroutineExceptionHandler { coroutineContext, throwable ->
                                              Log.e("异常抛出", throwable.message.toString())
                                          }
                              
                                      findViewById<Button>(R.id.btn).setOnClickListener {
                                          CoroutineScope(Dispatchers.IO+exceptionHandler).launch {
                                                  async {
                                                          throw Exception()
                                                  }.await()
                              
                                              launch {
                                                  while (true) {
                                                      delay(1000)
                                                      LogUtils.i("running")
                                                  }
                                              }
                                          }
                              
                                      }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              12
                              13
                              14
                              15
                              16
                              17
                              18
                              19
                              20

                              # 4. 协程种不应被取消的操作

                              在Android中我们使用Jetpack提供的CoroutineScope:viewModelScope或lifecycleScope,当Activity、fragment、Lifecycle结束时,会取消正在进行的工作。

                              如果我们需要工作在未完成前不被取消,应该怎么做?

                              • 如果需要某个操作的生命周期长于app,那么就使用WorkManager, 例如向服务器发送日志。

                              • 如果时只需要app还活着,协程就一直运行,app杀死时就可以取消的操作,那么就使用协程。

                              class MyApplication : Application() {
                                //不需要取消该Scope,因为它会随着进程死亡而终止。
                                val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
                              }
                              
                              1
                              2
                              3
                              4

                              使用launch或async启动协程

                              launch:

                              class Repository(
                                private val externalScope: CoroutineScope,
                                private val ioDispatcher: CoroutineDispatcher
                              ) {
                                suspend fun doWork() {
                                  withContext(ioDispatcher) {
                                    doSomeOtherWork()
                                    externalScope.launch {
                                      //如果这里可能会抛异常,那么请用try.catch把这里包起来,或者定义一个CoroutineExceptionHandler在externalScope的CoroutineContext中
                                      veryImportantOperation()
                                    }.join()
                                  }
                                }
                              }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              12
                              13
                              14

                              async:

                              class Repository(
                                private val externalScope: CoroutineScope,
                                private val ioDispatcher: CoroutineDispatcher
                              ) {
                                suspend fun doWork(): Any { // Use a specific type in Result
                                  withContext(ioDispatcher) {
                                    doSomeOtherWork()
                                    return externalScope.async {
                                      //调用await时会暴露异常,异常将在调用doWork的协程中传播。如果调用doWork处的协程已经cancel,把me该异常将被忽略。
                                      veryImportantOperation()
                                    }.await()
                                  }
                                }
                              }
                              
                              1
                              2
                              3
                              4
                              5
                              6
                              7
                              8
                              9
                              10
                              11
                              12
                              13
                              14
                              edit icon编辑此页open in new window
                              上次编辑于: 2022/6/14 23:00:37
                              贡献者: zsqan
                              上一页
                              协程
                              默认页脚
                              Copyright © 2022 Able