Kotlin协程入门(coroutine)

1.kotlin协程简介

Kotlin协程的主要作用是像写同步代码一样写异步代码。
避免回调地狱。

2.kotlin协程的特点

可控制:协程能做到可被控制的发起子任务
轻量级:协程非常小,占用资源比线程还小
语法糖:使多任务或多线程切换不再使用语法糖

3.启动协程的方式

  • 启动协程需要添加依赖
// Gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'

1:runBlocking:T // 用于执行协程任务,通常只用于启动最外层协程
2:launch:Job // 用于执行协程任务
3:async/await:Deferred // 用于执行协程任务,并执行得到结果

launch、async 需要实现 CoroutineScope 接口通过CoroutineScope 来开启协程

// CoroutineScope 让MainActivity带有作用域
class MainActivity : AppCompatActivity(), CoroutineScope {
	    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main
}
  override fun onDestroy() {
  	// 这样在 Activity 退出的时候,对应的作用域就会被取消,所有在该 Activity 中发起的请求都会被取消掉。
        cancel()
        super.onDestroy()
    }

3.1 runBlocking
  • runBlocking 的作用于最外层,也就是作用于协程的范围。可以建立一个阻塞当前线程的协程. 所以它主要被用来在main函数中或者测试中使用, 作为连接函数.
  • 假设一个三方登录的场景
    第一步:请求token
    第二步:请求用户权限
    第三步:三方登陆

三次请求都按照前后顺序成功以后才算登录成功。
假设三个线程对应三个请求

 class RequestToken : Runnable {
        override fun run() {
            Log.e(TAG, "RequestToken")
        }
    }

    class RequestUserPermission : Runnable {
        override fun run() {
            Thread.sleep(200)
            Log.e(TAG, "RequestUserPermission")
        }
    }

    class SignWithWX : Runnable {
        override fun run() {
            Log.e(TAG, "SignWithWX")
        }
    }

采用回调的方式是这样的。

 RequestToken(){
			// 请求token回调
            successCallBack(
            	// 请求权限
                RequestUserPermission(){
                	// 请求permission回调
                    successCallBack(){
                    	// 三方登录
                        SignWithWX(){
                        	// 三方登录回调
                            // success
                        }
                    }
                }
            )
        }

上面的代码只是简单的写了成功的回调还没有处理失败的回调,这种回调嵌套的代码看着感觉很乱
下面采用 runBlocking 的方式是这样的。runBlocking 默认是在当前线程开启协程的。runBlocking会阻塞当前线程,等到runBlocking全部执行完成后才会继续执行

	private fun login() = runBlocking {
        RequestToken().run()
        RequestUserPermission().run()
        SignWithWX().run()
        Log.e(TAG, "--end--")
    }

上面runBlocking的代码执行顺序Log如下:即使RequestUserPermission 在请求的时候 sleep了200依然是按照顺序执行的

MainActivity: RequestToken
MainActivity: RequestUserPermission
MainActivity: SignWithWX
MainActivity: --end--

非协程的线程中可以用Thread.sleep() 来使线程睡眠,协程中可以用 delay(100) 方法。非协程模块中是禁止调用 delay()方法的。

3.2 launch

CoroutineScope的扩展函数,返回Job。launch可以开启协程,也可以在其他协程中开启协程。**launch不会阻塞当前线程。**这个和runBlocking是有区别的

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的方式如下

    private fun loadMain() = launch(Dispatchers.Main) {
		val name = Thread.currentThread().name
        Log.e(TAG, "Dispatchers.Main-->$name")
    }
    
    private fun loadIO() = launch(Dispatchers.IO) {
        val name = Thread.currentThread().name
        Log.e(TAG, "Dispatchers.IO-->$name")
    }
    
    private fun loadDefault() = launch(Dispatchers.Default) {
        val name = Thread.currentThread().name
        Log.e(TAG, "Dispatchers.Default-->$name")
    }

    private fun loadUnconfined() = launch(Dispatchers.Unconfined) {
        val name = Thread.currentThread().name
        Log.e(TAG, "Dispatchers.Unconfined-->$name")
    }

launch有几个参数可以选择,代表了协程在哪个线程中执行,以及协程中断以后是在哪个线程中恢复。

  • Dispatchers.Main // 主线程
  • Dispatchers.IO // 采用on-demand创建的线程池, 用于网络或者是读写文件的工作.
  • Dispatchers.Default // 代表使用JVM上的共享线程池, 其大小由CPU核数决定, 不过即便是单核也有两个线程. 通常用来做CPU密集型工作
  • Dispatchers.Unconfined // 不指定特定线程默认当前线程
    打印的日志如下:
Dispatchers.IO-->DefaultDispatcher-worker-1
Dispatchers.Unconfined-->main
Dispatchers.Default-->DefaultDispatcher-worker-2
Dispatchers.Main-->main
  • launch 返回的是一个 job 提供了一些方法来控制和获取任务的属性
	start()
	cancel()
	cancel()
    join()
3.3 async/await

async 和 launch 一样都是用来创建一个 Coroutine 的。通过 async 函数返回的 Defeered 对象可以获取 Coroutine 的返回值。

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
}

	// start = CoroutineStart.LAZY 需要通过 start开启 
   val d1 = async(start = CoroutineStart.LAZY) { RequestUserPermission().run() }
        val d2 = async(start = CoroutineStart.LAZY) { SignWithWX().run() }
        d1.start()
  }
  • async{} 会在对应的 CoroutineContext 下创建一个新的协程,并且放回一个Deferred,通过 Deferred 可以异步获取结果,也就是调用Deffered 的 await() 方法。
    下面代码Log打印出的值为 “testReturn”
    非 start = CoroutineStart.LAZY 不用调用 start() 方法开启
 private fun loadMain() = launch {
        val d1 = async<String>{ testReturn() }
        val result = d1.await()
        Log.e(TAG, "await=$result")
    }

    private fun testReturn(): String {
        return "testReturn"
    }

await()方法是 suspend 修饰的方法,Kotlin协程中只有这一个关键字。suspend修饰的方法只能被suspend修饰的方法调用。

  public suspend fun await(): T

在 launch 里面会创建一个新的 CoroutineContext,如果没有传入 Context 则使用的EmptyCoroutineContext,通过 newCoroutineContext() 函数会分配一个默认的 Dispatcher,也就是Dispatcher.default,默认的全局 Dispatcher,会在jvm 层级共享线程池,会创建等于cpu 内核数目的线(但是至少创建两个子线程)。接着判断 CoroutineStart 是否 Lazy 模式,如果 Lazy 模式,则该Coroutine 不会立马执行,需要你主动掉了 Job.start() 之后才会执行。

  • suspend 修饰的函数或者 lambda 只能被 suspend 修饰的函数 lambda 调用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章