Kotlin—Coroutine(協(xié)程)的基本使用

Kotlin—Coroutine(協(xié)程)的基本使用

什么是協(xié)程

在java中異步都會使用到線程,在kotlin中引入了協(xié)程的概念。與線程類似,協(xié)程也是用于處理異步的,不過與線程相比更加輕巧。協(xié)程完全通過編譯技術(shù)實現(xiàn),使用掛起機制來實現(xiàn)異步,而不會阻塞線程。協(xié)程是一種避免線程阻塞、開銷更小且更加可控的異步操作。

協(xié)程的基礎(chǔ)使用

創(chuàng)建協(xié)程,有三種方式runBlocking、launch、async

  • runBlocking 創(chuàng)建一個阻塞的協(xié)程,當(dāng)協(xié)程內(nèi)部代碼執(zhí)行完畢后才會執(zhí)行后面的代碼。

    fun main() {
        println("Hello")
        runBlocking {
            delay(1000)
            println("World")
        }
        println("---end---")
    }
    

    runBlocking 創(chuàng)建的協(xié)程里面,調(diào)用 delay讓此協(xié)程掛起1秒。執(zhí)行代碼,首先打印Hello,1秒后打印World,最后打印—end—。注意delay掛起函數(shù)只有在協(xié)程內(nèi)部才能調(diào)用

  • launch 在當(dāng)前協(xié)程作用域下面創(chuàng)建一個非阻塞子協(xié)程,同時返回個 Job 對象,用來控制當(dāng)前協(xié)程。

    fun main() = runBlocking {
        println("hello")
        launch {
            delay(1000)
            println("world")
        }
        println("---end---")
    }
    

    使用 runBlocking 來包裹 main 函數(shù),函數(shù)整體都處于runBlocking創(chuàng)建的協(xié)程作用域下面,然后通過launch 創(chuàng)建一個子協(xié)程,掛起1秒之后打印輸出。執(zhí)行代碼發(fā)現(xiàn)首先輸出hello和—end—,等待一秒之后打印world,如何讓他按順序執(zhí)行。

    fun main() = runBlocking {
        println("hello")
        val job = launch {
            delay(1000)
            println("world")
        }
        job.join()
        println("---end---")
    }
    

    修改代碼,拿到 launch 返回的job對象,調(diào)用 join() 函數(shù),此時會等待job協(xié)程內(nèi)部的代碼執(zhí)行完畢后才會往后執(zhí)行。執(zhí)行代碼,先打印hello,然后1秒后打印world和—end—。

  • Job 常用方法

    • isActive
    • isCompleted
    • isCanceledstart
    • cancel
    • join
  • GlobalScope.launch 創(chuàng)建一個全局非阻塞協(xié)程,

    fun main() {
        println("hello")
        GlobalScope.launch {
              delay(1000)
            println("world")
        }
        Thread.sleep(1500)
        println("---end---")
    }
    

    使用 GlobalScope.launch 在任何地方都能創(chuàng)建一個全局的協(xié)程,由于 launch 創(chuàng)建的協(xié)程是非阻塞的,所以讓當(dāng)前線程睡眠1.5秒等待協(xié)程執(zhí)行完畢。與上面代碼打印結(jié)果一樣。由于使用 GlobalScope.launch 時,會創(chuàng)建一個頂層協(xié)程。雖然很輕量,但它運行時仍會消耗一些內(nèi)存資源,所以通常是在協(xié)程內(nèi)部作用域下使用launch創(chuàng)建協(xié)程,而不是使用GlobalScope來創(chuàng)建全局協(xié)程。

  • async 創(chuàng)建一個非阻塞協(xié)程,同時返回Deferred ,可通過 await() 函數(shù)獲取協(xié)程返回的具體值;

    fun main() = runBlocking {
        println("hello")
        val def = async {
            delay(1000)
            "world"
        }
        println(def.await())
        println("---end---")
    }
    
  • 掛起函數(shù)

    接著上面例子,將 launch 協(xié)程內(nèi)部的代碼封裝為一個函數(shù),使用suspend關(guān)鍵,這個函數(shù)就叫 掛起函數(shù),上面的 delay 就是一個封裝好的掛起函數(shù)。

    fun main() = runBlocking {
        println("hello")
        val job = launch {
            delay1000()
        }
        job.join()
        println("---end---")
    }
    
     suspend fun delay1000(){
        delay(1000)
        println("world")
    }
    
  • 調(diào)度器

    上面提到三種創(chuàng)建協(xié)程的方式都有一個選填參數(shù) CoroutineContext,協(xié)程的上下文環(huán)境。也就是協(xié)程調(diào)度器,調(diào)度器確定了協(xié)程執(zhí)行的線程環(huán)境。

    • Dispatchers.Default 默認后臺線程池里的線程 ;
    • Dispatchers.Main Android主線程;
    • Dispatchers.IO 后臺線程池里的IO線程 ;
    • Dispatchers.Unconfined 不限制,使用父協(xié)程所屬的線程;
    • newSingleThreadContext 使用新的線程。

    如需要在協(xié)程中切換調(diào)度器可使用withContext,withContext返回最后一行代碼的返回值。

  • Android中的使用

    簡單了介紹了協(xié)程的基本使用,寫個小demo,模擬網(wǎng)絡(luò)請求然后再界面展示數(shù)據(jù)。在activity中首先要實現(xiàn)CoroutineScope 接口,然后通過by關(guān)鍵字將接口的具體實現(xiàn)委托給 MainScope,這樣當(dāng)前activity就是一個協(xié)程作用域了。

    class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            button.setOnClickListener {
                showData()
            }
        }
      // 創(chuàng)建協(xié)程,默認在當(dāng)前線程,也就是UI線程
        private fun showData() = launch {
            progress.visibility = View.VISIBLE
            textView.text = loadData()
            progress.visibility = View.GONE
        }
    
      //網(wǎng)絡(luò)請求 切換為IO線程
        private suspend fun loadData()= withContext(Dispatchers.IO) {
            delay(2000)//掛起2秒 模擬網(wǎng)絡(luò)請求
            "假裝這是網(wǎng)絡(luò)請求到的數(shù)據(jù)" //返回請求到的數(shù)據(jù)
        }
    }
    

基本介紹完了協(xié)程的最最最基礎(chǔ)的使用,相比于線程除了內(nèi)存開銷更小、不會造成線程阻塞的優(yōu)點之外之外,個人覺得在沒有用RxJava的情況下可以少寫很多線程的創(chuàng)建、切換、異步接口的回調(diào)。讓代碼更加干凈清爽。
個人學(xué)習(xí)筆記,如有錯誤不對的地方歡迎各位大佬指出。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容