show code block

2023年11月29日 星期三

協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程

再強調一次

協程就是由kotlin官方所提供的線程api

  
        //Thread
        Thread {

        }.start()

        //Executor
        val executor = Executors.newCachedThreadPool()
        executor.execute {

        }

        //coroutine
        launch {

        }
  
  
Rxjava都有了 coroutine的優勢在哪?
本質上跟其他的線程api一樣,方便
他借助了kotlin的語言優勢,所以他基於那些Java之上的方案會更方便一點
最重要的是
他可以用看起來同步的方式,寫出異步代碼

  
   
        val user = api.getUser()//後台線程
        tvName.text = user.name //主線程
  
  

這就是kotlin最有名的『非阻塞式掛起』suspend
Coroutine最大的好處,是你可以把不同線程的代碼寫在同一塊代碼塊裡面
coroutine:
  
   
        launch(Dispatchers.Main) { // (主線程)
        
            val result = api.fetchData() // 異步操作,比如獲取網絡數據(後台線程)
            tvName.text = result.name // 在這裡處理結果,如更新UI(主線程)
            //上下兩行代碼,可以先切走在切回來,這是java完全做不到的事情
            
        }

  
正常callback寫法:
  
   
        api.getUser()
        .enqueue(object:Callback<User>{
        ...
        override fun onResponse(call:Call<User>,
                                response:Response<User>){
        
          runOnUiThread{
          
          tvName.text = response.body().name
          
          }
                               
         }
        
        })

  
Callback的寫法我們早就爛到骨頭裡了,你為什麼還是要學習Coroutine?
省了超多callback代碼,超簡潔 
消除了callback 多線程的操作難度直接抹平了 
不是10 -> 9 
而是 1 -> 0

我舉一個差異最大的例子
像是我要做一個
兩次網路請求
並把這兩個輸出結果拿去做第三個網路請求

callback式的開發,要做這種工作非常困難
於是我們可能會選擇妥協
變成完成a後再call b的先後請求,這就是標準的垃圾代碼了
明明可以並行的兩個請求,由於我自身能力不足
我讓網路時間等待長了一倍,也就是性能上差了一倍

如果是使用協程
  
launch(Dispatchers.Main) {

    val userDetails = async {

        api.getUserDetails(user) // 獲取用戶詳細資料

    }

    val userPosts = async {

        api.getUserPosts(user) // 獲取用戶發布的帖子

    }

    val combinedData = suspendingMerge(userDetails, userPosts) // 合併資料
    // 接下來可以處理合併後的資料

}
 
coroutine由於消除了併發之間協作的難度
可以輕鬆地寫出複雜的併發代碼
甚至有些不可能實現的併發任務,變成可能甚至變得很簡單
這些才是協程的優勢所在

但這時你可能會有個疑惑
你要在後台執行任務?切協程
要在主線程執行任務?切協程
  
 
          launch(Dispatchers.IO) {
          
              val fileData = readFile(fileId) // 在IO線程中讀取文件數據

              launch(Dispatchers.Main) {
                  fileTextView.setText(fileData) // 在主線程中更新文本視圖
              }
              
          }

   
 這不也是callback地獄?
 讓code很髒很不簡潔?
這邊就要講到
coroutine有一個很厲害的函數 withContext
  
          launch(Dispatchers.Main) {
              val fileContent = withContext(Dispatchers.IO) {
                  readFile(fileId) // 在IO線程中讀取文件內容
              }

              fileTextView.setText(fileContent) // 在主線程中設置文本視圖的內容
          }
 
可以寫成這樣,看起來區別不大
但如果你有更多的線程切換,優勢就體現出來了

由於有自動切回的功能,Coroutine消除了併發代碼在協作時的嵌套
 
        launch(Dispatchers.IO){
            ...
            launch(Dispatchers.Main){
                ...
                launch(Dispatchers.IO){
                    ...
                    launch(Dispatchers.Main){
                    }
                }
            }
        }
   
由於有了『自動切回來的功能』
寫成消除了在併發代碼協作時的嵌套
直接寫成上下關係代碼
就能讓Coroutine之間進行協作
 
        launch(Dispatchers.Main){
            withContext(Dispatchers.IO){
                ...
            }
                ...
            withContext(Dispatchers.IO){
                ...
            }
                ...
        }
     
這就是協程 
『協作式的例程』

而且由於消除了嵌套
你可以把withContext放到函數裡面
用她包著函數的實際業務代碼
但function要在前方用掛起函數
 
        launch(Dispatchers.Main) {
        
            val fileContent = suspendingReadFile(fileId)
            fileTextView.setText(fileContent)
            
        }

        suspend fun suspendingReadFile(fileId: String): String {
        
            return withContext(Dispatchers.IO) {
                readFile(fileId) // 在IO線程中讀取文件
            }
            
        }

     
suspend是coroutine最核心的關鍵詞
他也是最難懂,最多被誤解的點
『suspend是掛起,非阻塞式的,他並不會阻擋你的線程』
這你看得懂嗎? 我一開始是看不懂

再說一次
協程是什麼?
一個線程框架

好在哪?好在方便
最方便的地方在哪?
他可以在同一個代碼塊中做線程切換














沒有留言:

張貼留言

協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程 再強調一次 協程就是由kotlin官方所提供的線程api //Thread Thread { }.start() //Executor val execu...