Kotlin学习笔记21 协程part1 基本概念

参考链接

示例来自bilibili Kotlin语言深入解析 张龙老师的视频

本节先介绍协程的相关概念 概念可能枯燥,我们先要了解协程中的相关概念 然后结合代码理解这些概念 加深印象

协程的定义

协程通过将复杂性放入库中来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库为我们解决异步性

协程库可以将用户代码地相关部分包装为回调、订阅相关事件(listener)、在不同地线程(甚至不同的机器)上调度,而代码如同顺序执行一样简单

协程的描述

协程就像是轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大

而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎没有代价,协程由开发者控制。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意多个协程

总之:协程可以简化异步编程,可以顺序表达程序,协程也提供了一种避免阻塞线程并用更廉价更可控的操作代替线程阻塞的方法--协程挂起

1.协程的几个概念

CoroutingScope

CoroutingScope可以理解为协程本身 包含了CoroutingContext

CoroutingContext

CoroutingContext 协程上下文,是一些元素的集合,主要包括Job和CoroutingDispatcher元素,可以代表一个协程的场景

EmptyCoroutingContext

EmptyCoroutingContext表示一个空的协程上下文

CoroutingDispatcher

CoroutingDispatcher,协程调度器,决定协程所在的线程或线程池。他可以指定协程运行于特定的一个线程、一个线程池或者不指定任何线程(这样的协程运行于当前线程)

coroutings-core中CoroutingDispatcher有四种标准实现:

Dispatchers.Default

Dispatchers.IO:运行在IO线程

Dispatchers.Main:运行在主线程

Dispatchers.Unconfined:不指定线程

launch函数定义如果不指定CoroutingDispatcher或者没有其他的ContinuationInterceptor,默认的协程调度器就是Dispatchers.Default,Default是一个协程调度器,其指定的线程为共有的线程池,线程数量至少为2,最大数目于CPU数相同

Job & Defferred

Job,任务,封装了协程中需要执行的代码逻辑。Job可以取消并且有简单的生命周期,他有三种状态

 

Job完成时没有返回值,如果需要返回值的话,应该使用Defferred,他是Job的子类

public interface Defferred<out T>:Job

Coroutine builders

CoroutineScope.launch函数属于协程构建器Coroutine builders,Kotlin中还有其他几种协程构建器,负责创建协程

CoroutineScope.launch{}

CoroutineScope.launch{} 是最常用的Coroutine builders协程构建器,不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器

runBlocking{}

runBlocking{}是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,起作用主要是为main函数和测试设计的

withContext{}

withContext{}不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直到代码块运行完毕

async{}

CoroutineScope.async{}可以实现与launch builder一样的效果,在后台创建一个新的协程,唯一的区别是他又返回值,因为CoroutineScope.async{}返回的是Deferred类型

注意:开始写协程的demo前 需要自行引入Kotlin协程的依赖 Kotlin考虑到有的项目可能不需要使用协程,普通的Kotlin依赖和协程的依赖是分开的,因此我们需要单独引入协程相关的依赖

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"

1.3.9是当前我编写代码时的最新协程版本

完整module的build.gradle如下:

plugins {
    id 'java-library'
    id 'kotlin'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect:1.5.20"
    // 引入协程依赖
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
}

2 CoroutineScope.launch使用

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// 协程的第一个示例
// CoroutineScope.launch{}
// 协程结合线程
fun main() {
    /**
     * GlobalScope继承自CoroutineScope
     * CoroutineScope.launch{} 是最常用的Coroutine builders协程构建器,不阻塞当前线程,在后台创建一个新协程
     * 此外 我们没有指定CoroutingDispatcher 因此协程运行于当前线程 注意 协程依附于线程,若线程结束 则协程不复存在
     */
    GlobalScope.launch{// 在后台启动一个新的协程并继续
        delay(1000)// 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("Kotlin coroutine")// 在延迟后打印输出
    }

    println("hello")// 协程已在等待时主线程还在继续
    Thread.sleep(2000)// 阻塞主线程 2 秒钟来保证 JVM 存活
    println("world")// 最后输出
}

/**
 * 输出
 * hello
 * Kotlin coroutine
 * world
 * 分析:
 * GlobalScope.launch开辟一个协程 不阻塞当前线程 继续执行main线程 同时协程开始计时
 * 输出hello
 * main线程进入睡眠 开始计时 此时 协程和线程都在计时
 * 协程计时1s时间到 输出Kotlin coroutine
 * 又过了1s main线程计时总共2s 计时时间到 输出world
 *
 * 扩展
 * 思考 如果将Thread.sleep(2000)修改为 Thread.sleep(500) 会是输出什么呢?
 *
 * 答案输出:
 * hello
 * world
 *
 * 分析:因为协程计时需要1s 主线程计时只计时到0.5s 因此主线程计时完毕时 协程就不存在了
 * 因此没有机会输出Kotlin coroutine
 */

class HelloKotlin1 {
}

3.CoroutineScope.launch{} 使用线程的等价写法

import kotlin.concurrent.thread

/**
 * CoroutineScope.launch{} 使用线程的等价写法
 * 使用纯线程实现HelloKotlin1
 */
fun main() {

    // thread是Kotlin的一个方法 接受若干参数 最后一个参数是一个lambda表达式 他是thread的执行体
    // thread创建的线程默认自动执行
    thread {
        Thread.sleep(1000)// 子线程不会阻塞主线程 计时1s
        println("Kotlin coroutine")
    }

    println("hello")// 立即输出
    Thread.sleep(2000)// 阻塞主线程2s
    println("world")// 最后输出
}


/**
 * 以下代码复制自kotlin.concurrent.Thread.kt
 *
 * Creates a thread that runs the specified [block] of code.
 * 创建一个线程 其运行体是参数block
 *
 * @param start if `true`, the thread is immediately started.
 * 参数start如果为true 线程会立即执行
 * @param isDaemon if `true`, the thread is created as a daemon thread. The Java Virtual Machine exits when
 * the only threads running are all daemon threads.
 * 参数isDaemon如果是true 线程会被创建为守护线程。Java虚拟机会在所有存活线程都是守护线程的时候退出
 * @param contextClassLoader the class loader to use for loading classes and resources in this thread.
 * 参数contextClassLoader 当前线程用来加载类和资源的类加载器
 * @param name the name of the thread.
 * 参数name 当前线程的名称
 * @param priority the priority of the thread.
 * 参数priority 当前线程的优先级
 */
public fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread {
    val thread = object : Thread() {
        public override fun run() {
            block()
        }
    }
    if (isDaemon)
        thread.isDaemon = true
    if (priority > 0)
        thread.priority = priority
    if (name != null)
        thread.name = name
    if (contextClassLoader != null)
        thread.contextClassLoader = contextClassLoader
    if (start)
        thread.start()
    return thread
}

class HelloKotlin2 {
}

4.runBlocking

import kotlinx.coroutines.*

/**
 * runBlocking{}
 * 使用纯协程实现HelloKotlin1
 */
fun main() {
    GlobalScope.launch {//GlobalScope.launch创建协程 并且不阻塞当前线程 协程计时1s
        delay(1000)
        println("Kotlin coroutine")
    }

    println("hello")

    runBlocking {
        delay(2000)
    }
    println("world")
}

/**
 * 输出
 * hello
 * Kotlin coroutine
 * world
 *
 * 输出分析:
 *
 * 主线程输出hello
 * 主线程利用runBlocking创建一个协程 该协程阻塞主线程 协程需要计时2s
 * GlobalScope.launch计时1s完毕 输出Kotlin coroutine
 * 再过一秒  runBlocking创建的协程执行完毕 主线程不再阻塞 输出world
 */


/**
 * runBlocking详解
 * runBlocking是kotlin的一个方法 其注释摘抄如下
 *
 * 返回一个新的协程并且阻塞当前线程,阻断直到这个协程执行完毕
 * Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion.
 * 该方法不应该在协程中使用。他被设计来 桥接常规阻塞代码与以挂起风格写成的库 或是使用在main方法和test中
 * This function should not be used from a coroutine. It is designed to bridge regular blocking code
 * to libraries that are written in suspending style, to be used in `main` functions and in tests.
 *
 *
 * 当前builder的默认CoroutineDispatcher是事件循环的内部实现 它处理此阻塞线程中的延续,直到此协程完成。
 * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations
 * in this blocked thread until the completion of this coroutine.
 * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`.
 *
 * 当在 [context] 中明确指定 [CoroutineDispatcher] 时,新的协程会在当前线程被阻塞的同时在指定调度程序的上下文中运行。 如果指定的调度程序是另一个 `runBlocking` 的事件循环,则该调用使用外部事件循环。
 * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of
 * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`,
 * then this invocation uses the outer event loop.
 *
 * 如果当前阻塞线程被打断 那么协程job会被取消 并且它的runBlocking调用会抛出InterruptedException
 * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and
 * this `runBlocking` invocation throws [InterruptedException].
 *
 * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available
 * for a newly created coroutine.
 *
 * 参数 context: 协程上下文 默认值是当前线程的一个事件循环
 * @param context the context of the coroutine. The default value is an event loop on the current thread.
 * 参数 block: 协程运行体
 * @param block the coroutine code.
 *
 * @Throws(InterruptedException::class)
 */
class HelloKotlin3 {

}

5.CoroutineScope CoroutineScope.launch CoroutineScope.launch启动方式等

import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.*
import kotlinx.coroutines.launch
import kotlin.coroutines.ContinuationInterceptor
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

/**
 * 前面的用到知识点详解
 */

fun main() {
    GlobalScope.launch{

    }
}

// CoroutineScope详解
// 作用:为一个新的协程定义一个作用域 以及嵌套协程的规则
// 以下摘自Kotlin源码1.3.9
val CoroutineScopeExplain = """
kotlinx.coroutines CoroutineScope.kt 
public interface CoroutineScope
为一个新的协程定义一个作用域。每一个协程构建器(例如launch async等)都是CoroutineScope的一个扩展,并且继承了它的coroutineContext来自动传播CoroutineScope的上下文元素和可取消的特性
Defines a scope for new coroutines. Every coroutine builder (like launch, async, etc) is an extension on CoroutineScope and inherits its coroutineContext to automatically propagate all its elements and cancellation.
获取独立的scope的最佳方式是通过CoroutineScope() 以及 MainScope()等工厂方法。额外的上下文元素可以使用加运算符append到scope
The best ways to obtain a standalone instance of the scope are CoroutineScope() and MainScope() factory functions. Additional context elements can be appended to the scope using the plus operator.
Convention for structured concurrency
手动实现CoroutineScope接口是不推荐的,更推荐使用委托。习惯上,scope的上下文应该包含一个job的实例,以确保强制结构化并发(以便通过取消传播来强制执行结构化并发的规则。)
Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead. By convention, the context of a scope should contain an instance of a job to enforce the discipline of structured concurrency with propagation of cancellation.
每一个协程构建器(例如launch, async等等)以及每一个作用域方法(例如coroutineScope, withContext等等)都提供了自己的scope和它自己的Job实例到它的运行的内部代码块中。 根据约定,他们都等待他们块内的所有协程完成,然后再完成自己(协程嵌套协程 外部的协程要等到内部的协程执行完毕 才能结束自身)从而强制执行结构化并发
Every coroutine builder (like launch, async, etc) and every scoping function (like coroutineScope, withContext, etc) provides its own scope with its own Job instance into the inner block of code it runs. By convention, they all wait for all the coroutines inside their block to complete before completing themselves, thus enforcing the structured concurrency. See Job documentation for more details.
Android usage
Android has first-party support for coroutine scope in all entities with the lifecycle. See the corresponding documentation .
Custom usage
CoroutineScope应当在一个实体中被实现或声明为一个属性 并且有一个定义良好的生命周期 他们负责启动子的协程(这种实体在Android中有一个好的例子 就是Activity)
CoroutineScope should be implemented or declared as a property on entities with a well-defined lifecycle that are responsible for launching children coroutines, for example:
class MyUIClass {
    val scope = MainScope() // the scope of MyUIClass

    fun destroy() { // destroys an instance of MyUIClass
        scope.cancel() // cancels all coroutines launched in this scope
        // ... do the rest of cleanup here ...
    }

    /*
     * 注意:如果当前实例被销毁 或者任何已经启动的协程在launch内部抛出异常,那么所有嵌套的协程会被取消
     * Note: if this instance is destroyed or any of the launched coroutines
     * in this method throws an exception, then all nested coroutines are cancelled.
     */
        fun showSomeData() = scope.launch { // launched in the main thread
           // ... here we can use suspending functions or coroutine builders with other dispatchers
           draw(data) // draw in the main thread
        }
    }
""".trimIndent()

/**
 * CoroutineScope.launch详解
 * CoroutineScope.launch 启动一个新的协程而不会阻塞当前线程 默认情况下 协程会立刻调度执行
 * 启动行为通过start指定
 */
val CoroutineScopeDotLaunch = """
    可以看到CoroutineScope.launch接受三个参数 前两个参数有默认值 返回一个Job对象
    suspend关键字用来定义一个挂起函数 该关键字只能用在协程内部或者一个suspend函数中
    kotlinx.coroutines Builders.common.kt public fun CoroutineScope.launch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() → Unit
    ): Job
 * 启动一个新的协程而不会阻塞当前线程 并且以Job的形式返回当前协程的引用
 * Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job].
 * 当生成的job被取消时 当前协程也会取消
 * The coroutine is cancelled when the resulting job is [cancelled][Job.cancel].
 *
 * 协程上下文继承自CoroutineScope。额外的上下文元素可以通过context参数指定。
 * The coroutine context is inherited from a [CoroutineScope]. Additional context elements can be specified with [context] argument.
 * 如果上下文没有指定dispatcher调度器/分发器 或者任何其他ContinuationInterceptor() 那么默认会使用Dispatchers.Default
 * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
 * 父Job也继承自CoroutineScope 但是它也可以被一个对应的上下文元素覆盖掉
 * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
 * with a corresponding [context] element.
 *
 * 默认情况下 协程会立刻调度执行
 * By default, the coroutine is immediately scheduled for execution.
 * 其他开启选项可以通过start参数指定
 * Other start options can be specified via `start` parameter. See [CoroutineStart] for details.
 * 一个可选的 start参数是CoroutineStart.LAZY以延迟启动协程 在这种情况下 协程Job被创建为 new state
 * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
 * 它可以通过调用Job.start方法被显示地启动 也可以在第一次调用Job.join时被隐式启动
 * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
 * and will be started implicitly on the first invocation of [join][Job.join].
 *
 * 当前协程地未捕获异常默认会取消当前上下文地父job
 * Uncaught exceptions in this coroutine cancel the parent job in the context by default
 * (除非CoroutineExceptionHandler被指定) 这意味着当launch被其他协程地上下文调用时 任何未捕获异常都会导致父协程取消
 * (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with
 * the context of another coroutine, then any uncaught exception leads to the cancellation of the parent coroutine.
 *
 * See [newCoroutineContext] for a description of debugging facilities that are available for a newly created coroutine.
 *
 * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
 * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
 * @param block the coroutine code which will be invoked in the context of the provided scope.

""".trimIndent()

/**
 * CoroutineScope.launch第二个参数start详解
 */
val launchStart ="""
    启动方式时枚举类型CoroutineStart的实例
 * Defines start options for coroutines builders.
 * 定义了协程构建器的启动方式 它被用在CoroutineScope.launch CoroutineScope.async以及其他构建器方法的start参数中
 * It is used in `start` parameter of [launch][CoroutineScope.launch], [async][CoroutineScope.async], and other coroutine builder functions.
 *
 * The summary of coroutine start options is:
 *                立刻根据上下文调度协程执行
 * * [DEFAULT] -- immediately schedules coroutine for execution according to its context;
                延时启动协程 只有需要的时候才启动 
 * * [LAZY] -- starts coroutine lazily, only when it is needed;
                 根据上下为自动(以不可取消的方式)调度协程
 * * [ATOMIC] -- atomically (in a non-cancellable way) schedules coroutine for execution according to its context;
                 立即执行协程直到遇到它在当前线程的第一个挂起点
 * * [UNDISPATCHED] -- immediately executes coroutine until its first suspension point _in the current thread_.    
""".trimIndent()


class HelloKotlin3_2 {
}

6.delay

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

/**
 * delay
 */
fun main() = runBlocking {// 开始执行主协程
    GlobalScope.launch {// 在后台用新的协程构建器 启动一个新的协程并继续
        delay(1000)
        println("Kotlin coroutine")
    }
    println("hello")// 主协程在这里会立即执行
    delay(2000)// 延迟 2 秒来保证 JVM 存活
    println("world")// 最后输出
}

/**
 * 输出
 * hello
 * Kotlin coroutine
 * world
 */

class HelloKotlin4 {
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值