Kotlin协程:CoroutineScope 作用域

作用域CoroutineScope

    • 1、作用域概括
    • 2、普通函数
    • 3、作用域CoroutineScope
    • 4、扩展函数/扩展属性
      • ①、launch、async
      • ②、join、await
      • ③、其他扩展
        • <1>、cancle()
        • <2>、ensureActive()、isActive
        • <3>、plus()
    • 5、实现类
      • ①、核心库
      • ②、平台支持
      • ③、工厂函数
    • 6、协程作用域函数
      • ①、coroutineScope( )
      • ②、supervisorScope( )
      • ③、withContext( )
      • ④、withTimeout( )
      • ⑤、withTimeoutOrNull( )
    • 7、创建协程的区别

1、作用域概括

Kotlin协程:CoroutineScope 作用域_第1张图片

从思维脑图你应该知道的事:

  • 作用域CoroutineScope是一个接口,但是他有一个CoroutineScope工厂函数可以自定义作用域这个不要搞混了
  • 作用域CoroutineScope有扩展函数、扩展属性意味着所有的实现类、工厂函数等都可以调用这些扩展函数、属性
  • 挂起函数只能在挂起函数或者作用域中调用,所以协程作用域挂起函数都只能在作用域中调用
  • 协程作用域函数只能构建子协程(不考虑runBlocking,这个函数只做测试)
  • 协程构建器能创建根协程或子协程

2、普通函数

普通函数 runBlocking()

public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
	//......
}
  • 会阻塞当前线程直到其内部所有协程执行完毕,内部的协程彼此之间依旧是非阻式。
  • 用于把阻塞式的普通函数内部改为协程式编写。

注意:会阻塞线程在开发中不会使用,一般用于main函数作改成协程或者测试函数改成协程进行测试,目的就是函数内部提供一个协程作用域

@Test
fun coroutineStudy(): Unit = runBlocking {
	//测试用例函数
}
fun main() :Unit= runBlocking{
    //main函数
}

3、作用域CoroutineScope

是一个接口,没有任何抽象方法需要实现,仅仅维护一个成员变量 CoroutineContext(协程上下文),将作为初始上下文对象传递给被创建的协程,不同的实现类或协程作用域函数本质上的区别是持有的协程上下文不同(配置不同)

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

4、扩展函数/扩展属性

①、launch、async

Kotlin协程:CoroutineScope 作用域_第2张图片

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,//默认空上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,//默认启动模式DEFAULT
    block: suspend CoroutineScope.() -> Unit
): Job {
    //......
}
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,//默认空上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,//默认启动模式DEFAULT
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    //......
}

启动模式可以看这篇文章

launch() 和 async() 是 CoroutineScope 接口的扩展函数,继承了它的 CoroutineContext来自传播其上下文元素和可取消性。

launch() 和 async()都能构建跟协程,但是

async不推荐当成跟协程
原因:一般使用async都是为了拿到返回值,而await又是挂起函数,无法获取返回值就跟launch一样,所以不推荐async当做跟协程

GlobalScope.launch {
     //核心库launch构建协程 
     launch { 
     	//launch 子协程
     }
     async{ 
     	//async 子协程
     }       
}

MainScope().launch { 
    //工厂函数launch构建协程 
    launch { 
     	//launch 子协程
     }
     async{ 
     	//async 子协程
     }            
}

viewModelScope.launch {
	//jetpack 支持库viewMode launch构建协程
	launch { 
     	//launch 子协程
     }
     async{ 
     	//async 子协程
     }     
}


//虽然async能构建跟协程,但是不推荐
GlobalScope.async{
     //核心库async构建协程 
     launch { 
     	//launch 子协程
     }
     async{ 
     	//async 子协程
     }           
}

MainScope().async{ 
    //工厂函数async构建协程 
    launch { 
     	//launch 子协程
     }
     async{ 
     	//async 子协程
     }            
}

viewModelScope.async{
	//jetpack 支持库viewMode async构建协程
	launch { 
     	//launch 子协程
     }
     async{ 
     	//async 子协程
     }     
}

②、join、await

示例1:三个协程如下,运行情况

@Test
fun launchStudy(): Unit = runBlocking {

    val job1 = launch {
        delay(3000)
        println("第一个协程执行 success")
    }

    val job2 = launch {
        delay(2000)
        println("第二个协程执行 success")
    }

    val job3 = launch {
        delay(1000)
        println("第三个协程执行 success")
    }
}

//执行结果
第三个协程执行 success
第二个协程执行 success
第一个协程执行 success

示例2:如果想让着三个launch协程按顺序执行,就需要用join让程序等待协程执行完成

@Test
fun launchStudy(): Unit = runBlocking {

    val job1 = launch {
        delay(3000)
        println("第一个协程执行 success")
    }
    job1.join()

    val job2 = launch {
        delay(2000)
        println("第二个协程执行 success")
    }
    job2.join()

    val job3 = launch {
        delay(1000)
        println("第三个协程执行 success")
    }
    job3.join()
}
//执行结果
第一个协程执行 success
第二个协程执行 success
第三个协程执行 success

示例3:如果想让着三个async 协程按顺序执行,就需要用await让程序等待协程执行完成,await能拿到协程的返回值

@Test
fun asyncStudy(): Unit = runBlocking {

    val deferred1 = async {
        delay(3000)
        "第一个协程执行 success" //返回值
    }
    //deferred1.join() 调用join也是可以的,但是拿不到返回值
    println(deferred1.await()) //获取返回值

    val deferred2 = async {
        delay(2000)
        "第二个协程执行 success" //返回值
    }
    //deferred2.join()
    println(deferred2.await()) //获取返回值

    val deferred3 = async {
        delay(1000)
        "第三个协程执行 success" //返回值
    }
    //deferred3.join()
    println(deferred3.await()) //获取返回值
}

//执行结果
第一个协程执行 success
第二个协程执行 success
第三个协程执行 success

示例4:通常我们不会像示例3一样,因为我们多线程编程就是为了尽量节省时长,正确使用 await 实现多线程并发如下

@Test
fun asyncStudy(): Unit = runBlocking {

    val deferred1 = async {
        delay(3000)
        "第一个协程执行 success"
    }

    val deferred2 = async {
        delay(2000)
        "第二个协程执行 success"
    }

    val deferred3 = async {
        delay(1000)
        "第三个协程执行 success"
    }

    println("${deferred1.await()},${deferred2.await()},${deferred3.await()}")

}

//执行结果
第一个协程执行 success,第二个协程执行 success,第三个协程执行 success

③、其他扩展

Kotlin协程:CoroutineScope 作用域_第3张图片

<1>、cancle()
public fun cancel(cause: CancellationException? = null)

cancle() 函数的作用就是通过构建协程返回的Job、deferred来取消协程

  • 取消父协程,子协程也会跟着取消
  • 取消可以传参数添加一个异常描述,默认为空

示例1:取消单个协程

@Test
fun launchStudy(): Unit = runBlocking {

    val job1 = launch {
        println("第一个协程执行 start")
        delay(2000)
        println("第一个协程执行 success")
    }
    job1.cancel()

    val job2 = launch {
        println("第二个协程执行 start")
        delay(1000)
        println("第二个协程执行 success")
    }
    job2.cancel()
}

//执行输出
没有任何输出,调度前协程被取消

示例2:将第二个协程当成第一协程的子协程,取消协程1

@Test
fun launchStudy(): Unit = runBlocking {

    val job1 = launch {
        println("第一个协程执行 start")
        delay(2000)
        println("第一个协程执行 success")

        val job2 = launch {
            println("第二个协程执行 start")
            delay(1000)
            println("第二个协程执行 success")
        }
    }
    job1.cancel()
}

//执行输出
没有任何输出,调度前协程被取消

示例3:主动取消协程,定义取消描述

@Test
fun launchStudy(): Unit = runBlocking {
    val job1 = launch {
        try {
            println("第一个协程执行 start")
            delay(2000)
            println("第一个协程执行 success")
        } catch (e: CancellationException) {
            println("协程取消 $e")
        }
    }
    delay(1)//让协程进入执行阶段
    job1.cancel("我主动取消协程")
}

//执行输出
第一个协程执行 start
协程取消 java.util.concurrent.CancellationException: 我主动取消协程
<2>、ensureActive()、isActive

扩展函数 ensureActive:ensureAlive 实际上是对 isActive 封装

public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()

//ensureAlive 实际上是对 isActive 封装
public fun Job.ensureActive(): Unit {
    if (!isActive) throw getCancellationException()
}

扩展属性 isActive

public val CoroutineScope.isActive: Boolean
    get() = coroutineContext[Job]?.isActive ?: true

如果协程进入CPU密集型计算的场景,这个时候调用cancle是无效的,因为这个时候线程的执行权已经被CPU密集型计算占满了,所以根本没有资源给你执行cancle指令,这个时候就需要用到 ensureAlive()、isAlive 来检测协程生命周期状态

示例1:模拟CPU密集型计算,执行下面代码程序会卡死~~

@Test
fun coroutineStudy(): Unit = runBlocking {
    var item = 0
    val job = launch(Dispatchers.Default) {
        println("launch is start")
        val currentTimeMillis = System.currentTimeMillis()
        while (item < Int.MAX_VALUE) {
            if (System.currentTimeMillis() > currentTimeMillis) {
                println("current item=${item++}")
            }
        }
    }
    delay(1)//让协程进入执行阶段
    //调度前取消
    job.cancel()
}

示例2:解决方案1使用 isAlive
结果:执行一段时间 协程取消

@Test
fun coroutineStudy(): Unit = runBlocking {

    var item = 0

    val job = launch(Dispatchers.Default) {
        println("launch is start")
        val currentTimeMillis = System.currentTimeMillis()
        while (item < Int.MAX_VALUE && isActive) {//每次循环检测一下状态
            if (System.currentTimeMillis() > currentTimeMillis) {
                println("current item=${item++}")
            }
        }
    }
    delay(1)
    //调度前取消
    job.cancel()

}

示例3:解决方案2使用 ensureActive
结果:执行一段时间 协程取消

@Test
fun coroutineStudy2(): Unit = runBlocking {

    var item = 0

    val job = launch(Dispatchers.Default) {
        println("launch is start")
        val currentTimeMillis = System.currentTimeMillis()
        while (item < Int.MAX_VALUE) {
            ensureActive()
            if (System.currentTimeMillis() > currentTimeMillis) {
                println("current item=${item++}")
            }
        }
    }

    delay(1)
    //调度前取消
    job.cancel()

}
<3>、plus()

用来将指定的上下文元素合并(已存在同类型会覆盖)

public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
    ContextScope(coroutineContext + context)

5、实现类

GlobalScope、MainScope、lifecycleScope、viewModelScope构建协程用法都是相同的,一般用来构建一个根协程。

作用域 说明 建议
GlobalScope 全局作用域,跟随整个进程的生命周期 不推荐使用
MainScope 跟Activity/Fragment生命周期挂钩 推荐使用
LifecycleOwner.lifecycleScope 跟Lifecycle生命周期挂钩,由jetpack提供 推荐使用
ViewModel.viewModelScope 跟ViewModel生命周期挂钩,由jetpack提供 推荐使用

①、核心库

GlobalScope:单例对象,不推荐使用。全局协程作用域,不绑定到任何Job上无法取消,通过它启动的子协程不会阻塞其所在线程可以一直运行到APP停止,子协程运行在自己的调度器上不会继承上下文与父协程没有联系,因此所有开启的协程都需要分别手动来管理。

public object GlobalScope : CoroutineScope {
 	//...
}

ContextScope:上下文作用域,根据指定的上下文创建协程作用域。使用工厂函数 MainScope()、CoroutineScope() 传入上下文对象参数,获取到的就是 ContextScope 实例。

internal class ContextScope(context: CoroutineContext) : CoroutineScope {
	//...
}

②、平台支持

ViewModel.viewModelScope:由jetpack提供,在 ViewModel 销毁时协程作用域会自动被 cancel,避免造成协程泄漏(内存泄漏)。

public val ViewModel.viewModelScope: CoroutineScope

LifecycleOwner.lifecycleScope :由jetpack提供,在 Activity/Fragment 中使用,UI 销毁时协程作用域会自动被 cancel,避免造成协程泄漏(内存泄漏)。

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope

③、工厂函数

MainScope:该调度程序会绑定到主线程,在 onDestroy() 中调用 cancel() 关闭协程。可用于主动控制协程的生命周期,对Android开发意义在于避免内存泄漏。

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

示例:

class StudyActivity : AppCompatActivity() {

     private val mainScope by lazy {
         MainScope()
     }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        
        mainScope.launch { 
            println("mainScope is start")
        }
    }

    override fun onDestroy() {
        super.onDestroy()

        mainScope.cancel()
    }
}

CoroutineScope:根据自定义上下文创建协程作用域,不包含Job则自动创建以。CoroutineScope是一个只包含 coroutineContext 属性的接口,虽然我们可以创建一个实现类但这不是一个流行的做法,而且存在不小心在其它地方取消作用域。通常我们会更喜欢通过对象来启动协程,最简单的办法是使用 CoroutineScope() 工厂函数,它用传入的上下文来创建作用域(如果上下文中没有 Job 会自动创建一个用于结构化并发)。

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())
@Test
fun coroutineTest(): Unit= runBlocking {

    //CoroutineScope 是工厂模式创建的
    val coroutineScope = CoroutineScope(Job()+ Dispatchers.Default)
    //自定义的作用域
    println("调度前。。。。")

    coroutineScope.launch {
        println("launch1 start")
        delay(1000)
        println("launch1 end")
    }

    coroutineScope.launch {
        println("launch2 start")
        delay(1000)
        println("launch2 end")
    }

    delay(500)
    coroutineScope.cancel()

    //这样创建协程的意义就是,让结构化并发更具有稳定性,可以同时控制取消
}

//执行输出
调度前。。。。
launch1 start
launch2 start

6、协程作用域函数

Kotlin协程:CoroutineScope 作用域_第4张图片
都是挂起函数不会阻塞线程。由于挂起需要协程环境,只能由其它挂起函数或协程调用,因此只能用来创建子协程。

①、coroutineScope( )

public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
	//...
}

使用场景:经常被用来包装一个挂起函数的主体,多用于并行分解任务。

示例:协程3取消后,所有协程都取消了

@Test
fun coroutineScopeTest(): Unit = runBlocking {

    //一个子协程出现问题,其他兄弟协程全部取消
    coroutineScope {

        launch {
            println("launch 1 start")
            delay(1000)
            println("launch 1 end")
        }

        launch {
            println("launch 2 start")
            delay(1000)
            println("launch 2 end")
        }

        launch {
            println("launch 3 start")
            throw NullPointerException()
            delay(1000)
            println("launch 3 end")
        }
    }
}

//执行输出
launch 1 start
launch 2 start
launch 3 start

java.lang.NullPointerException
	at com.example.mycoroutine.Simple17$coroutineScopeTest$1$1$3.invokeSuspend(Simple17.kt:30)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

②、supervisorScope( )

public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
	//...
}

使用场景:主要用于启动多个独立任务。

示例:协程3取消后,其他兄弟协程正常运行

@Test
fun supervisorScopeTest(): Unit = runBlocking {

    //一个子协程出现问题,其他兄弟协程正常运行
    supervisorScope {

        launch {
            println("launch 1 start")
            delay(1000)
            println("launch 1 end")
        }

        launch {
            println("launch 2 start")
            delay(1000)
            println("launch 2 end")
        }

        launch {
            println("launch 3 start")
            throw NullPointerException()
            delay(1000)
            println("launch 3 end")
        }
    }
}

//执行输出
launch 1 start
launch 2 start
launch 3 start
Exception in thread "Test worker @coroutine#4" java.lang.NullPointerException
	at com.example.mycoroutine.Simple17$supervisorScopeTest$1$1$3.invokeSuspend(Simple17.kt:58)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
	
launch 1 end
launch 2 end

③、withContext( )

public suspend fun <T> withContext(context: CoroutineContext,block: suspend CoroutineScope.() -> T): T {
	//...
}

使用场景:经常用来指定协程执行的线程和启动模式

示例:通过withContext切换执行的线程

@Test
fun coroutineStudy4(): Unit = runBlocking {
    println("start "+Thread.currentThread().name)
    withContext(Dispatchers.IO) {
        //切换线程到IO线程运行
        println("withContext "+Thread.currentThread().name)
    }
}

//执行输出
start Test worker @coroutine#1
withContext DefaultDispatcher-worker-1 @coroutine#1

④、withTimeout( )

public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T {
	//...
}

使用场景:超时未执行完会抛异常,并返回一个值。超时抛出的TimeoutCancellationException

示例:6s超时协程,到时直接报错

@Test
fun coroutineStudy(): Unit = runBlocking {

    withTimeout(6000) {
        for (i in 1..Int.MAX_VALUE) {
            delay(1000)
            println("item i=$i")
        }
    }
}

//执行输出
item i=1
item i=2
item i=3
item i=4
item i=5

Timed out waiting for 6000 ms
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 6000 ms
	(Coroutine boundary)
	at com.example.mycoroutine.Simple28$coroutineStudy$1$1.invokeSuspend(Simple28.kt:16)
	at com.example.mycoroutine.Simple28$coroutineStudy$1.invokeSuspend(Simple28.kt:14)
Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 6000 ms

⑤、withTimeoutOrNull( )

public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T? {
	//...
}

使用场景:超时未执行完不抛异常,返回null。如网络请求超时

示例:6s超时后返回了 null

@Test
fun coroutineStudy3(): Unit = runBlocking {
    val withTimeoutOrNull = withTimeoutOrNull(6000) {
        for (i in 1..Int.MAX_VALUE) {
            delay(1000)
            println("item i=$i")
        }

        "执行完成"
    }
    println(withTimeoutOrNull ?: "执行超时")
}

//执行输出
item i=1
item i=2
item i=3
item i=4
item i=5
执行超时

7、创建协程的区别

Kotlin协程:CoroutineScope 作用域_第5张图片

协程构建器和协程作用域函数中都包含了形参 block: suspend CoroutineScope.() -> Unit

你可能感兴趣的:(Kotlin,协程,kotlin,android,开发语言,协程)