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 調用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章