协程学习(一)一个最简单的协程例子

从这篇文章开始,就进入了协程的学习阶段了,但是由于协程的学习是一个比较系统性的东西,他有很多需要了解的知识点 ,比如 suspend挂起 协程作用域Scope 协程体 调度器Dispatchers 等,当知识积累到一定程度再去阅读源码可能效果会更好一点,所以从协程这个板块,我打算从最简单的例子说起

下面上代码

kotlin 复制代码
object TsmTest {
    @JvmStatic
    fun main(arge: Array<String>) {
        GlobalScope.launch(Dispatchers.IO) {
            Thread.sleep(100)
            println("--------result----------${Thread.currentThread().name}")
        }
        println("--------方法结束----------")
        Thread.sleep(1000)
    }
}

这个是一个特别简单的方法,可以直接Run ,不必去打包android 就可以直接运行, 调试起来也比较简单

在这里有几个比较重要的知识点

CoroutineScope 作用域

在 android 中非常多的应用场景都有生命周期的这个概念,稍微不注意就会造成内存泄露的麻烦,但是如果去维护生命周期就会造成代码的臃肿和难以阅读,在这种场景下作用域的功能就体现出来了,

下面列举一下比较常用的作用域

1:如果你是在ViewModule 中使用协程可以使用 androidx.lifecycle:lifecycle-viewmodel-ktx 包下面为我们提供的 viewModelScope ,

viewModelScope 的默认调度器是 Dispatchers.Main

kotlin 复制代码
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

2:如果你是在Activity 或者Fragment 中使用可以使用 androidx.lifecycle:lifecycle-runtime-ktx 为我们提供的 lifecycleScope,让协程中的任务自动感知他们所处的环境,当宿主被回收后,任务自动与宿主隔离,

lifecycleScope 的默认调度器是 Dispatchers.Main

kotlin 复制代码
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }

3:GlobalScope 他的作用域就是 App 的整个生命周期,所以也可以说他没有作用域

GlobalScope 的默认调度器是 Dispatchers.Default

我们协程体中是可以获取到CoroutineScope的,在协程体中可以直接使用原因我们就需要看一下 launch 方法

kotlin 复制代码
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
}

CoroutineScope.launch 方法一共有3个参数,我们先不管前面2个参数,直接看第三个参数 ,他是一个被 suspend 关键字修饰的 block,同样的这个block 还是 CoroutineScope扩展的一个匿名函数,

根据上面的分析我们就可以将 上面代码改写成

kotlin 复制代码
var job= GlobalScope.launch(block = {
    Thread.sleep(100)
    println("--------result----------${Thread.currentThread().name}")

    this.cancel()
    this.isActive
})

图片中的两个this 代表的是样的含义,都是 CoroutineScope,既然 block 是 CoroutineScope 的匿名扩展方法,我们就可以使用这个 this 来取消或者得到当前协程的结果

关于 CoroutineScope 还有一些知识点,比如异常的传播,取消机制 等等,我们后边继续分析

launch async

GlobalScope.launch 会开启一段协程,但是他会返回一个 Job , 我们可以根据这个job 来获取和操控任务的状态,但是他没有结果,也就是无法通过 job获取结果,但是如果你要结果该如何使用呢. 没错 就是使用 async 方法

GlobalScope.async 会返回一个 Deferred 的对象,我们可以用 Deferred.await 方法来获取 协程的返回结果,需要了解到的是 Deferred 他是 Job 的子类

Dispatchers.IO 调度器

调度器决定了协程体中的代码的执行时所挂载到的线程, 默认是 Dispatchers.Default ,但是这里面有一个问题,那就是 Dispatchers.IO 与 Dispatchers.Default 有什么不同吗,下面就把 源码中 Dispatchers.IO 的注释拿出来

vbnet 复制代码
/**
 * The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads.
 *
 * Additional threads in this pool are created and are shutdown on demand.
 * The number of threads used by tasks in this dispatcher is limited by the value of
 * "`kotlinx.coroutines.io.parallelism`" ([IO_PARALLELISM_PROPERTY_NAME]) system property.
 * It defaults to the limit of 64 threads or the number of cores (whichever is larger).
 *
 * Moreover, the maximum configurable number of threads is capped by the
 * `kotlinx.coroutines.scheduler.max.pool.size` system property.
 * If you need a higher number of parallel threads,
 * you should use a custom dispatcher backed by your own thread pool.
 *
 * ### Implementation note
 *
 * This dispatcher shares threads with a [Default][Dispatchers.Default] dispatcher, so using
 * `withContext(Dispatchers.IO) { ... }` does not lead to an actual switching to another thread &mdash;
 * typically execution continues in the same thread.
 * As a result of thread sharing, more than 64 (default parallelism) threads can be created (but not used)
 * during operations over IO dispatcher.
 */
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO

这段话的意思就是 DefaultScheduler.IO 是为了加载一些 IO 任务的共享线程池,他确定了最大线程数, 但是从线程意义与 Dispatchers.Default 上来说是没有区别的,如果当前协程体的调度器是 Dispatchers.Default ,如果你使用 withContext(Dispatchers.IO) ,那么他不会进行线程调度,

用比较通俗的话来讲就是 Dispatchers.Default 是运算密集型, 而 Dispatchers.IO 是任务密集型 ,但是这两个调度器之间不能来回切换,没有什么太大的意义

相关推荐
工业甲酰苯胺1 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee3 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯3 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey4 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!6 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟7 小时前
Android音频采集
android·音视频
小白也想学C8 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程8 小时前
初级数据结构——树
android·java·数据结构
闲暇部落10 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin