1. 程式人生 > >Kotlin開發利器之協程

Kotlin開發利器之協程

Kotlin開發利器之協程

協程的定義

  協程的開發人員 Roman Elizarov 是這樣描述協程的:協程就像非常輕量級的執行緒。執行緒是由系統排程的,執行緒切換或執行緒阻塞的開銷都比較大。而協程依賴於執行緒,但是協程掛起時不需要阻塞執行緒,幾乎是無代價的,協程是由開發者控制的。所以協程也像使用者態的執行緒,非常輕量級,一個執行緒中可以建立任意個協程。

專案中引入Kotlin的協程

  新增依賴:coroutines庫在Kotlin1.3版本的時候已經升級為正式版,命名為1.0.0。

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
// 可以新增Android的依賴
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'

1.協程方法的開啟

GlobalScope.launch

fun main(args: Array<String>) {
    GlobalScope.launch { // 在後臺啟動一個新的協程並繼續
        delay(1000L) // 非阻塞執行緒1s
        println("World!") // 在延遲後列印輸出
        println("This is a coroutines ${TimeUtil.getTimeDetail()}")
    }
    println("Hello,") // 主執行緒的協程將會繼續等待
    Thread.sleep(2000L) // 阻塞執行緒2s,保證JVM存活,協程可正常執行完
    println("main end ${TimeUtil.getTimeDetail()}")
}
  
// 輸出
// Hello,
// World!
// This is a coroutines 10:49:58
// main end 10:49:59

線上程環境中可直接使用CoroutineScope.launch啟動一個新的協程,它的引數有如下三個,分別為:

  • context: CoroutineContext = EmptyCoroutineContext:協程上下文;
  • start: CoroutineStart = CoroutineStart.DEFAULT:啟動模式,預設是DEAFAULT,也就是建立就啟動;還有一個是LAZY,意思是等你需要它的時候,再呼叫啟動。在Kotlin 1.3版本中,還有ATOMIC和UNDISPATCHED兩個額外的模式,但是現在還是實驗版,這裡不多介紹;
  • block: suspend CoroutineScope.() -> Unit:閉包引數,定義協程內需要執行的操作。

  返回值為Job物件。Job有如下幾個重要的方法,分別為:
  job.start()可配合LAZY啟動一個協程

fun main(args: Array<String>){
    val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
        println("this is a job")
    }
    job.start()

    Thread.sleep(1000L)
}

// 輸出
// this is a job

  job.join()等待協程執行完畢

suspend fun main(args: Array<String>){
    val job = GlobalScope.launch {
        println("this is a job")
    }
    job.join()
}

// 輸出
// this is a job

  注意:join()函式是一個掛起函式,所有main必須被suspend修飾。
  job.cancel()取消一個協程

suspend fun main(args: Array<String>){
    val job = GlobalScope.launch {
        println("this is a job")
    }
    job.cancel()
    job.join()
}

// 無輸出,協程被取消了

  job.cancelAndJoin()等待協程執行完畢然後再取消

suspend fun main(args: Array<String>){
    val job = GlobalScope.launch {
        println("this is a job")
    }
    job.cancelAndJoin()
}

// 輸出
// this is a job

  job.cancelAndJoin()也是一個掛起函式。
  從上面的程式碼和輸出可以看到,協程中的輸出和main中的輸出只相差了1s,也就說明了為什麼delay(1000L)是非阻塞的。delay()函式類似於Thread.sleep(),但是它不阻塞(non-blocking)執行緒,它是一個被suspend修飾的掛起函式,掛起函式只能被掛起函式呼叫或協程中呼叫。

2.GlobalScope.async

suspend fun main(args: Array<String>) {
    val deferred = GlobalScope.async {
        delay(1000L)
        println("This is async ${TimeUtil.getTimeDetail()}")
        [email protected] "taonce"
    }
    println("main start ${TimeUtil.getTimeDetail()}")
    val result = deferred.await()
    println("async result is $result")
    println("main end ${TimeUtil.getTimeDetail()}")
}
// 輸出
// main start 15:27:19:668
// This is async 15:27:20:652
// async result is taonce
// main end 15:27:20:657

  async和launch引數是一模一樣的,不同的是async返回的是Deferred物件,它繼承了Job介面,所以說Job有的它都有,並且還額外增加了一個方法:
public suspend fun await(): T這個方法接收的是async閉包中返回的值。如果閉包中需要返回一個值那麼我們就需要考慮用async了。

3.runBlocking

  runBlocking的最大特點就是它的delay()可以阻塞當前的執行緒,和Thread.sleep()有著相同的效果。一般用runBlocking來橋接普通阻塞程式碼和掛起風格的非阻塞程式碼,在runBlocking閉包裡面啟動另外的協程。

協程實際運用

  以最常用的網路請求為例,如果不用Kotlin的協程來實現,我們可以用Thread、RxJava等等很多種方法。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        coroutine.setOnClickListener { click() }
    }

    private fun click() = runBlocking {
        GlobalScope.launch(Dispatchers.Main) {
            coroutine.text = GlobalScope.async(Dispatchers.IO) {
                // 比如進行了網路請求
                // 放回了請求後的結構
                [email protected] "main"
            }.await()
        }
    }
}

  上面程式碼的原理是:用async()在IO執行緒中去執行網路請求,然後通過await()返回請求結果,最後用launch(Dispatchers.Main)在主執行緒中更新UI。
  其中用到了Dispatchers來指定協程所在的執行緒,目前Dispatchers有三種:

  • Default:如果沒有指定具體的Dispatchers都會使用預設的,它使用的是最大的執行緒數;
  • IO:用來排程阻塞的協程;
  • Main:就是常說的UI執行緒了,是Android特有的。

總結

  • 本質上,協程是輕量級的執行緒;
  • 掛起函式(被suspend修飾的函式)不能在普通函式中被呼叫;
  • 可以使用一些協程操作來替換一些執行緒操作,比如: 用 GlobalScope.launch { …… } 替換 thread { …… } 用 delay(……) 替換 Thread.sleep(……);
  • 協程是輕量級的,比開啟執行緒節約資源;
  • 在 GlobalScope 中啟動的活動中的協程就像守護執行緒一樣,不能使它們所在的程序保活。