协程

可简单的理解为轻量级的线程

协程与线程:

  • 都能实现多线程的效果
  • 线程依靠操作系统的调度才能实现不同线程之间的切换
  • 协程仅在编程语言的层面就能实现不同协程之间的切换,大大提升了并发编程的运行效率

协程允许我们在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全由编程语言来控制,和操作系统无关。

协程的使用

在安卓开发的过程中使用需要添加依赖库

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x'



GlobalScope.launch

GlobalScope.launch函数可以创建一个协程作用域(永远是顶层协程),函数的Lambda表达式就是在协程中运行。应用程序的结束这个协程作用域也会跟着一起结束。

GlobalScope.launch { 
        //协程作用域
}

delay()
delay()函数可以让当前协程延迟指定时间后再运行,是非阻塞式挂起函数,只能在协程的作用域或者其它挂起函数中调用,而且它只挂起当前协程,并不影响其它协程的运行。



runBlocking

runBlocking函数可创建一个协程的作用域,它可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程。

runBlocking { 
   //协程作用域
}

runBlocking函数我们通常只在测试环境中使用,因为它会挂起外部线程,程序的性能会受到影响。



launch

launch函数能创建一个子协程作用域,但是只能在协程作用域下创建,如果在GlobalScope.launch函数的作用域下创建,一旦外层作用域结束,那么该作用域下的所有子协程也都会结束。

GlobalScope.launch {
  //协程作用域
  launch { 
    //子协程作用域
  }
  launch { 
    //子协程作用域
  }
}



suspend & coroutineScope

suspend
suspend可将任意函数声明为挂起函数。
因为挂起函数之间可以互相调用,所以当launch中逻辑复杂时,如果我们想把其中一部分代码提取出来到另一个函数中,就可以使用suspend将这个函数声明为挂起函数,launch就可以直接调用了。

coroutineScope
coroutineScope为挂起函数,在协程作用域或者挂起函数中调用,它会继承外部协程的作用域并创建一个子协程。会保证其作用域内的所有代码和子协程在全部执行完毕之前,外部协程会一直被挂起。

suspend fun A()=coroutineScope {
    launch { 
    
    }
}



async

必须在协程作用域中才能调用,它会创建一个新的子协程并返回一个Deferred对象,调用Deferred对象的await()方法可获取async函数代码块的执行结果。

val result=async {
    1+1
}.await()

当调用await()方法时,如果代码块中的代码还没有执行完,那么await()会阻塞当前协程,直到可以获得async函数的执行结果。
在通常使用中我们推荐withContext()函数,更加简洁。

withContext

val result= withContext(Dispatchers.Default) {
    1 + 1
}

调用withContext函数后,立即执行,同时将外部协程挂起,当代码执行完会将最后一行执行结果作为返回值返回。
withContext中传入了一个参数,它是指定的线程参数,在安卓开发中会强制我们指定一个。

线程参数
安卓的网络请求必须在子线程中进行,仅仅依靠协程是完全不行的,所以我们需要通过线程参数给协程指定一个具体的运行线程。

  • Dispatchers.Default 低并发
  • Dispatchers.IO 高并发
  • Dispatchers.Main 不会开启线程

除了 coroutineScope外,以上所有可开启协程域的函数,都可以指定线程参数。



实际项目中

在实际项目中我们的协程构建方式与基本方式不同。

GlobalScope.launch每一次都会创建一个顶层协程,内部可以创建子协程,GlobalScope.launch和launch都会返回一个Job对象,Job对象可以用来取消协程。当因为某些原因,我们不需要协程在运行下去的时候,可以使用job.cancel()去取消。如果是这样,每一个协程作用域都会返回一个Job对象,那么就需要逐个去取消每一个协程,非常的麻烦。

优化后的构建方式:

//创建一个Job对象
val job=Job() 

//传入CoroutineScope中返回一个 CoroutineScope实例
val scope= CoroutineScope(job)  

//调用 CoroutineScope实例的launch函数来创建协程,然后所有的协程都被关联在这一个job下了
scope.launch {

}

//使用一个cancel()就可以取消所有创建的协程
job.cancel()