• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

协程入门基础

武飞扬头像
流金岁月5789651
帮助2

概念

协程是一种非抢占式或者说协作式的计算机程序并发调度的实现,程序可以主动挂起或者恢复执行。

函数或者一段程序能够被挂起(可以理解成暂停),待会儿再恢复,挂起和恢复是开发者的程序逻辑自己控制的,协程是通过主动挂起出让运行权来实现协作的

协程和线程区别

  • 线程之间是抢占式的调度
  • 协程之间是协作式的调度

协程是依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的。所以协程像是一种用户态的线程,非常轻量级,一个线程中可以创建N个协程。

使用

依赖

 // 协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
    // 协程Android支持库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"

开启协程

使用 runBlocking 顶层函数

通常适用于单元测试的场景,业务开发中不会用到这种方法,因为它是线程阻塞的。

runBlocking {
    // 代码...
}

使用GlobalScope 对象

使用GlobalScope单例对象,可以直接调用launch、async开启协程。在Android开发中同样不推荐这种用法,因为它的生命周期会和app一致,且不能取消(和使用runBlocking的区别在于不会阻塞线程)。

GlobalScope.launch {
    // 代码...
}

GlobalScope.async {
    // 代码...
}

使用CoroutineScope 对象

通过 CoroutineContext创建一个CoroutineScope对象,需要一个类型为CoroutineContext 的参数(推荐的使用,通过 context 参数去管理和控制协程的生命周期)。

val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
     // 代码...
}

作用域

作用域(Coroutine Scope)是协程运行的作用范围。launch、async都是CoroutineScope的扩展函数,CoroutineScope定义了新启动的协程作用范围,同时会继承了他的coroutineContext自动传播其所有的 elements和取消操作。

实操

runBlocking 顶级函数

binding.tvBtn.setOnClickListener {
            val result= runBlocking {
                Log.e("CoroutineExActivity","---runBlocking--》启动协程")
                "使用runBlocking 启动协程"
            }

            Log.e("CoroutineExActivity","--runBlocking-->${result}")
}

// 执行结果
CoroutineExActivity: ---runBlocking--》启动协程
CoroutineExActivity: --runBlocking-->使用runBlocking 启动协程

runBlocking启动的是一个新的协程并阻塞调用它的线程,直到runBlocking运行结束才继续往下执行。并且在runBlocking协程最后一行增加一个返回值。

launch 函数

binding.tvBtn.setOnClickListener {
            val result=GlobalScope.launch {
                Log.e("CoroutineExActivity","-----》启动协程")
            }
            Log.e("CoroutineExActivity","---->${result}")
        }

// 执行结果(其中一种结果)
CoroutineExActivity: -----》启动协程
CoroutineExActivity: ---->StandaloneCoroutine{Completed}@b2fd55f

从上面的执行结果来看,launch方法返回StandaloneCoroutine类型的result。查看一下其源码:

// launch 源码

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
}

从源码来看,launch方法返回了Job类型。那执行结果StandaloneCoroutine和源码中定义方法返回类型Job之间有啥关联?查看StandaloneCoroutine源码:

// StandaloneCoroutine 源码

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
    ...
}

public abstract class AbstractCoroutine<in T>(
    /**
     * The context of the parent coroutine.
     */
    @JvmField
    protected val parentContext: CoroutineContext,
    active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    ...
}
学新通

从源码来看,StandaloneCoroutine是Job 的子类。

async 函数

 binding.tvBtn.setOnClickListener {
            val result=GlobalScope.async {
                Log.e("CoroutineExActivity","---async--》启动协程")
            }
            Log.e("CoroutineExActivity","--async-->${result}")
        }

// 执行结果(其中一种结果)
CoroutineExActivity: --async-->DeferredCoroutine{Active}@b2fd55f
CoroutineExActivity: ---async--》启动协程

从上面的执行结果来看,async方法返回DeferredCoroutine类型的result。查看一下其源码:

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

private open class DeferredCoroutine<T>(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<T>(parentContext, active), Deferred<T>, SelectClause1<T> {
    ...
}

public interface Deferred<out T> : Job {
    //返回结果值,或者如果延迟被取消,则抛出相应的异常
    public suspend fun await(): T  
    public val onAwait: SelectClause1<T>
    public fun getCompleted(): T
    public fun getCompletionExceptionOrNull(): Throwable?
}
学新通

从上面的源码来看,DeferredCoroutine是AbstractCoroutine的实现类,同时实现Deferred 接口。这么看来DeferredCoroutine就是一个Deferred,一个携带有返回值Job。 通过
Deferred 的await() 方法来获取返回值T。

// launch 源码 
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ...
}

//  async 源码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    ...
}
学新通

从launch和 async 方法源码来看,CoroutineContext代表上下文,它的作用就是线程切换,它的值有Dispatchers.Main、Dispatchers.IO、、Dispatchers.Default、Dispatchers.Unconfined。

CoroutineStart表示启动模式,其值有:CoroutineStart.DEFAULT、CoroutineStart.LAZY、CoroutineStart.ATOMIC、CoroutineStart.UNDISPATCHED,默认直接允许调度执行。各个值具体含义和用法在稍后文章中讲解。

// 第一种写法
coroutineScope.launch(Dispatchers.Main) {      // 在 UI 线程开始
    val image = withContext(Dispatchers.IO) {  //  切换到 IO 线程,并在执行完成后切回 UI 线程
        getImage(imageId)                      // 将会运行在 IO 线程
    }
    avatarIv.setImageBitmap(image)             // 回到 UI 线程更新 UI
} 


// 第二种方法
coroutineScope.launch(Dispatchers.Main) {// 在 UI 线程开始
     val image = getImage(imageId)    // 调用suspend 函数
     avatarIv.setImageBitmap(image)  // 回到 UI 线程更新 UI
}

// 挂起函数
suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
    ...
}
学新通

suspend 是 Kotlin 协程最核心的关键字,意思是可挂起(暂停)。挂起函数只能在协程中或者挂起函数中调用。

withContext()函数是一个比较特殊的作用域构建器,withContext()函数是一个挂起函数。调用
withContext()函数之后,会立即执行代码块中的代码,同时将当前协程阻塞住。当代码块中的代码全部执行完之后,会将最后一行的执行结构作为withContext()函数的返回值返回。

协程比线程轻量级原因

编程语言级别实现的协程就是程序内部的逻辑,不会涉及操作系统的资源之间的切换。操作系统的内核线程自然会重一些,且不说每创建一个线程就会开辟的栈带来的内存开销,线程在上下文切换的时候需要CPU把高速缓存清掉并从内存中替换下一个线程的内存数据,并且处理上一个内存的中断点保存就是一个开销很大的事儿。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgccehe
系列文章
更多 icon
同类精品
更多 icon
继续加载