Kotlin中的協(xié)程 - suspend

前言

Kotlin是一種在Java虛擬機上運行的靜態(tài)類型編程語言,被稱之為Android世界的Swift,在GoogleI/O2017中,Google宣布Kotlin成為Android官方開發(fā)語言

delay

delay是一個頂級函數(shù),由于它被suspend修飾,所以只能用在協(xié)程或者被其他suspend函數(shù)修飾,它的功能為

將當前協(xié)程延遲一個給定時間,但是不會阻塞當前線程 并且它也是可以被取消的

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
    }
}

我們再看下另一個會延遲的函數(shù)Thread.sleep()它會導(dǎo)致 當前線程進行休眠

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds plus the specified
     * number of nanoseconds, subject to the precision and accuracy of system
     * timers and schedulers. The thread does not lose ownership of any
     * monitors.
     */
    public static void sleep(long millis, int nanos)

在協(xié)程中使用它們,會有不同的效果,如果我們的協(xié)程都在同一個線程

fun test() {
    MainScope().launch {
        Log.e("Mike","Coroutine1 start")
        delay(2000)
        Log.e("Mike","Coroutine1 end")
    }

    MainScope().launch {
        Log.e("Mike","Coroutine2 start")
        delay(2000)
        Log.e("Mike","Coroutine2 end")
    }
}

上述的代碼在使用delay時打印,當前線程沒有阻塞會執(zhí)行Coroutine2

Coroutine1 start
Coroutine2 start
//兩秒后
Coroutine1 end
Coroutine2 end

上述的代碼在使用Thread.sleep()時打印

Coroutine1 start
//兩秒后
Coroutine1 end
Coroutine2 start
//兩秒后
Coroutine2 end

很容易看到這兩種用法的區(qū)別,并且當我們在協(xié)程中時如果使用了阻塞線程的Thread.sleep()也會有警告提示Inappropriate blocking method call 提示你使用了不適當?shù)淖枞椒?,因為線程阻塞會導(dǎo)致其他協(xié)程無法執(zhí)行,會影響其他協(xié)程,delay表示的是非阻塞調(diào)用,不會阻塞當前線程

非阻塞式掛起

我們很容易理解阻塞與非阻塞的區(qū)別,從行為上來講,就是是否擋住了你這條線程的后續(xù)執(zhí)行,如果擋住了就是阻塞,沒有擋住就是非阻塞,那么掛起是什么,掛起的行為其實就是切換了線程的工作

    MainScope().launch {
        delay(2000)
    }

    MainScope().launch {
        delay(2000)
    }

在上述例子中,第一個協(xié)程創(chuàng)建完成之后就會被掛起,主線程的執(zhí)行由當前協(xié)程切換到了下一個協(xié)程,當掛起兩秒之后再將主線程切換回了第一個協(xié)程繼續(xù)工作,掛起其實也就是一種協(xié)程的暫停行為,不會線程中的其他單元

suspend

suspend是協(xié)程中很重的關(guān)鍵字,它用來修飾函數(shù),表示此函數(shù)是一個會掛起的函數(shù),并且 掛起函數(shù)只有在協(xié)程中使用或者被另一個掛起函數(shù)調(diào)用,可以暫停和進行恢復(fù),什么情況下需要用到掛起函數(shù)

  • 線程切換,掛起本身是線程切換不同的協(xié)程去工作,所以當需要進行線程切換時可以使用掛起函數(shù)
  • 延時,暫停往往代表在等待一些結(jié)果,當我們在等待一些返回結(jié)果時,協(xié)程可以通過掛起的方式等待,而不阻塞線程

suspend只是對函數(shù)的一個標識別,它不像inline,refied等關(guān)鍵字一樣會對代碼造成影響,而是提醒使用者這是一個掛起函數(shù),具體的掛起業(yè)務(wù)還是需要函數(shù)內(nèi)部自己實現(xiàn)

withContext

withContext是一個掛起函數(shù),表明它只能在協(xié)程或者其他suspend函數(shù)調(diào)用

/**
 * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
 * the result.
 *
 * The resulting context for the [block] is derived by merging the current [coroutineContext] with the
 * specified [context] using `coroutineContext + context` (see [CoroutineContext.plus]).
 * This suspending function is cancellable. It immediately checks for cancellation of
 * the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive].
 *
 * This function uses dispatcher from the new context, shifting execution of the [block] into the
 * different thread if a new dispatcher is specified, and back to the original dispatcher
 * when it completes. Note that the result of `withContext` invocation is
 * dispatched into the original context in a cancellable way, which means that if the original [coroutineContext],
 * in which `withContext` was invoked, is cancelled by the time its dispatcher starts to execute the code,
 * it discards the result of `withContext` and throws [CancellationException].
 */
public suspend fun <T> withContext

需要傳入一個suspending 代碼塊,并且基于合并后的Context執(zhí)行環(huán)境,并且可以被取消,會返回代碼塊的執(zhí)行結(jié)果,在suspending 代碼塊執(zhí)行完畢之后有切換回來

fun test() {
    MainScope().launch {
        Log.e("Mike", "Coroutine start")
        val result = withContext(Dispatchers.IO) {
            delay(2000)
            "resposne data"
        }
        Log.e("Mike", "Coroutine end $result")
    }
}
打印結(jié)果
Coroutine start 主線程
兩秒后
Coroutine end resposne data 主線程

可以提取出suspend函數(shù),這樣我們的代碼看起來是同步單線程執(zhí)行的,但是實際卻在不同的線程

fun test() {
    MainScope().launch {
        Log.e("Mike", "Coroutine start") //主線程
        val result = getData() //getData在IO線程
        Log.e("Mike", "Coroutine end $result") //主線程
    }
}

suspend fun getData() = withContext(Dispatchers.IO) {
    delay(2000)
    "resposne data"
}

suspend使用案例

文件讀取

private val scope = CoroutineScope(Dispatchers.Main)

scope.launch {
    Toast.makeText(this@MainActivity, "start...", Toast.LENGTH_SHORT).show()
    val result = readFile()
    Toast.makeText(this@MainActivity, "end..." + result, Toast.LENGTH_LONG).show()
}

suspend fun readFile() = withContext(Dispatchers.IO) {
    val inp = assets.open("a.txt")
    val isr = InputStreamReader(inp)
    isr.readText().also {
        inp.close()
        isr.close()
    }
}

網(wǎng)絡(luò)請求

引入Okhttp

implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")

封裝OkhttpClient

object OkHttpManager {

    fun getOkhttpClient() = OkHttpClient.Builder()
        .addNetworkInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .build()
}

封裝Sevice

class RequestService(val okhttpClient: OkHttpClient) {

    suspend fun getInfo() = withContext(Dispatchers.IO) {
        val url = "https://www.baidu.com".toHttpUrl().newBuilder().build()
        val request = Request.Builder().url(url).build()
        okhttpClient.newCall(request).execute().body?.string()
    }
}

發(fā)起調(diào)用

scope.launch {
    Toast.makeText(this@MainActivity, "start...", Toast.LENGTH_SHORT).show()
    val result = RequestService(OkHttpManager.getOkhttpClient()).getInfo()
    Toast.makeText(this@MainActivity, "end..." + result, Toast.LENGTH_LONG).show()
}

歡迎關(guān)注Mike的簡書

Android 知識整理

?著作權(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)容