天天看點

Kotlin協程入門(coroutine)

文章目錄

      • 1.kotlin協程簡介
      • 2.kotlin協程的特點
      • 3.啟動協程的方式
          • 3.1 runBlocking
          • 3.2 launch
          • 3.3 async/await

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 調用