Kotlin中的协程 - suspend

前言

Kotlin是一种在Java虚拟机上运行的静态类型编程语言,被称之为Android世界的Swift,在GoogleI/O2017中,Google宣布Kotlin成为Android官方开发语言

delay

delay是一个顶级函数,由于它被suspend修饰,所以只能用在协程或者被其他suspend函数修饰,它的功能为

将当前协程延迟一个给定时间,但是不会阻塞当前线程 并且它也是可以被取消的

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

我们再看下另一个会延迟的函数Thread.sleep()它会导致 当前线程进行休眠

    /**
     * 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)

在协程中使用它们,会有不同的效果,如果我们的协程都在同一个线程

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时打印,当前线程没有阻塞会执行Coroutine2

Coroutine1 start
Coroutine2 start
//两秒后
Coroutine1 end
Coroutine2 end

上述的代码在使用Thread.sleep()时打印

Coroutine1 start
//两秒后
Coroutine1 end
Coroutine2 start
//两秒后
Coroutine2 end

很容易看到这两种用法的区别,并且当我们在协程中时如果使用了阻塞线程的Thread.sleep()也会有警告提示Inappropriate blocking method call 提示你使用了不适当的阻塞方法,因为线程阻塞会导致其他协程无法执行,会影响其他协程,delay表示的是非阻塞调用,不会阻塞当前线程

非阻塞式挂起

我们很容易理解阻塞与非阻塞的区别,从行为上来讲,就是是否挡住了你这条线程的后续执行,如果挡住了就是阻塞,没有挡住就是非阻塞,那么挂起是什么,挂起的行为其实就是切换了线程的工作

    MainScope().launch {
        delay(2000)
    }

    MainScope().launch {
        delay(2000)
    }

在上述例子中,第一个协程创建完成之后就会被挂起,主线程的执行由当前协程切换到了下一个协程,当挂起两秒之后再将主线程切换回了第一个协程继续工作,挂起其实也就是一种协程的暂停行为,不会线程中的其他单元

suspend

suspend是协程中很重的关键字,它用来修饰函数,表示此函数是一个会挂起的函数,并且 挂起函数只有在协程中使用或者被另一个挂起函数调用,可以暂停和进行恢复,什么情况下需要用到挂起函数

  • 线程切换,挂起本身是线程切换不同的协程去工作,所以当需要进行线程切换时可以使用挂起函数
  • 延时,暂停往往代表在等待一些结果,当我们在等待一些返回结果时,协程可以通过挂起的方式等待,而不阻塞线程

suspend只是对函数的一个标识别,它不像inline,refied等关键字一样会对代码造成影响,而是提醒使用者这是一个挂起函数,具体的挂起业务还是需要函数内部自己实现

withContext

withContext是一个挂起函数,表明它只能在协程或者其他suspend函数调用

/**
 * 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  withContext

需要传入一个suspending 代码块,并且基于合并后的Context执行环境,并且可以被取消,会返回代码块的执行结果,在suspending 代码块执行完毕之后有切换回来

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

可以提取出suspend函数,这样我们的代码看起来是同步单线程执行的,但是实际却在不同的线程

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()
    }
}

网络请求

引入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()
    }
}

发起调用

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()
}

欢迎关注Mike的

Android 知识整理

你可能感兴趣的:(Kotlin中的协程 - suspend)