我正在阅读Kotlin协程,并且知道它是基于挂起函数的。但是暂停是什么意思呢?

协程或函数被挂起?

从https://kotlinlang.org/docs/reference/coroutines.html

基本上,协程是可以挂起而不阻塞线程的计算

我经常听到人们说“暂停功能”。但我认为是协程被挂起,因为它正在等待函数完成?“suspend”通常意味着“停止操作”,在这种情况下,协程是空闲的。

我们是否应该说协程被挂起?

哪个协程被挂起?

从https://kotlinlang.org/docs/reference/coroutines.html

继续类比,await()可以是一个挂起函数(因此也可以从async{}块中调用),它挂起协程,直到完成一些计算并返回其结果:

async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

它说“这会挂起一个协程,直到一些计算完成”,但协程就像一个轻量级线程。那么,如果协程挂起,如何进行计算呢?

我们看到await在计算时被调用,所以返回Deferred的可能是异步的,这意味着它可以启动另一个协程

fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

这句话说挂起了一个协程。这意味着挂起外部异步协程,还是挂起内部计算协程?

挂起是否意味着当外部异步协程正在等待(await)内部计算协程完成时,它(外部异步协程)空闲(因此称为挂起)并返回线程池,当子计算协程完成时,它(外部异步协程)醒来,从池中取出另一个线程并继续?

我之所以提到这个线程是因为https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

当协程正在等待时,线程被返回到池中,当等待完成时,协程在池中的空闲线程上恢复


当前回答

对于那些仍然想知道如何挂起一个挂起函数的人,我们在挂起函数体中使用了suspendCoroutine函数。

    suspend fun foo() :Int
  {
    Log.d(TAG,"Starting suspension")
    return suspendCoroutine<Int> { num->

      val result = bar()
      Log.d(TAG,"Starting resumption")           
      num.resumeWith(Result.success(result))
    }

  }

fun bar():Int //this is a long runnning task

其他回答

既然已经有很多好的答案了,我想给其他人一个更简单的例子。

runBlocking用例:

myMethod() is suspend function runBlocking { } starts a Coroutine in blocking way. It is similar to how we were blocking normal threads with Thread class and notifying blocked threads after certain events. runBlocking { } does block the current executing thread, until the coroutine (body between {}) gets completed override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name); runBlocking { Log.d(TAG,"Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend"); myMethod(); } Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name); } private suspend fun myMethod() { withContext(Dispatchers.Default) { for(i in 1..5) { Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name); } }

输出:

I/TAG: Outer code started on Thread : main
D/TAG: Inner code started  on Thread : main making outer code suspend
// ---- main thread blocked here, it will wait until coroutine gets completed ----
D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- main thread resumes as coroutine is completed ----
I/TAG: Outer code resumed on Thread : main

启动用例:

launch { } starts a coroutine concurrently. This means that when we specify launch, a coroutine starts execution on worker thread. The worker thread and outer thread (from which we called launch { }) both runs concurrently. Internally, JVM may perform Preemptive Threading When we require multiple tasks to run in parallel, we can use this. There are scopes which specify lifetime of coroutine. If we specify GlobalScope, the coroutine will work until application lifetime ends. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name); GlobalScope.launch(Dispatchers.Default) { Log.d(TAG,"Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend"); myMethod(); } Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name); } private suspend fun myMethod() { withContext(Dispatchers.Default) { for(i in 1..5) { Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name); } } }

输出:

10806-10806/com.example.viewmodelapp I/TAG: Outer code started on Thread : main
10806-10806/com.example.viewmodelapp I/TAG: Outer code resumed on Thread : main
// ---- In this example, main had only 2 lines to execute. So, worker thread logs start only after main thread logs complete
// ---- In some cases, where main has more work to do, the worker thread logs get overlap with main thread logs
10806-10858/com.example.viewmodelapp D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-1 making outer code suspend
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-1

异步和等待用例:

When we have multiple tasks to do and they depend on other's completion, async and await would help. For example, in below code, there are 2 suspend functions myMethod() and myMethod2(). myMethod2() should get executed only after full completion of myMethod() OR myMethod2() depends on result of myMethod(), we can use async and await async starts a coroutine in parallel similar to launch. But, it provides a way to wait for one coroutine before starting another coroutine in parallel. That way is await(). async returns an instance of Deffered<T>. T would be Unit for default. When we need to wait for any async's completion, we need to call .await() on Deffered<T> instance of that async. Like in below example, we called innerAsync.await() which implies that the execution would get suspended until innerAsync gets completed. We can observe the same in output. The innerAsync gets completed first, which calls myMethod(). And then next async innerAsync2 starts, which calls myMethod2() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name); job = GlobalScope.launch(Dispatchers.Default) { innerAsync = async { Log.d(TAG, "Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend"); myMethod(); } innerAsync.await() innerAsync2 = async { Log.w(TAG, "Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend"); myMethod2(); } } Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name); } private suspend fun myMethod() { withContext(Dispatchers.Default) { for(i in 1..5) { Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name); } } } private suspend fun myMethod2() { withContext(Dispatchers.Default) { for(i in 1..10) { Log.w(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name); } } }

输出:

11814-11814/? I/TAG: Outer code started on Thread : main
11814-11814/? I/TAG: Outer code resumed on Thread : main
11814-11845/? D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-2 making outer code suspend
11814-11845/? D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- Due to await() call, innerAsync2 will start only after innerAsync gets completed
11814-11848/? W/TAG: Inner code started  on Thread : DefaultDispatcher-worker-4 making outer code suspend
11814-11848/? W/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 6 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 7 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 8 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 9 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 10 on Thread : DefaultDispatcher-worker-4

为了理解挂起协程到底意味着什么,我建议你看一下下面的代码:

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() {
    GlobalScope.launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

unrestricted协程调度程序消除了协程调度的魔力,允许我们直接关注裸协程。

作为启动调用的一部分,启动块内的代码立即在当前线程上开始执行。具体情况如下:

计算val a = a() 这将链到b(),到达suspendCoroutine。 函数b()执行传递给suspendCoroutine的块,然后返回一个特殊的COROUTINE_SUSPENDED值。通过Kotlin编程模型无法观察到这个值,但这正是编译后的Java方法所做的。 函数a()看到这个返回值,本身也返回它。 启动块做同样的事情,现在控制返回到启动调用之后的行:

注意,在这一点上,您可以获得与启动块内的代码和有趣的主代码同时执行相同的效果。所有这些都发生在单个本机线程上,因此启动块被“挂起”。

Now, inside the forEach looping code, the program reads the continuation that the b() function wrote and resumes it with the value of 10. resume() is implemented in such a way that it will be as if the suspendCoroutine call returned with the value you passed in. So you suddenly find yourself in the middle of executing b(). The value you passed to resume() gets assigned to i and checked against 0. If it's not zero, the while (true) loop goes on inside b(), again reaching suspendCoroutine, at which point your resume() call returns, and now you go through another looping step in forEach(). This goes on until finally you resume with 0, then the println statement runs and the program completes.

上面的分析应该给您一个重要的直观印象,即“挂起一个协程”意味着将控件返回到最内部的启动调用(或者更一般地说,是协程构建器)。如果协程在恢复后再次挂起,则resume()调用结束,控制权返回给resume()的调用者。

协程分派器的存在使得这种推理不那么清晰,因为它们中的大多数会立即将您的代码提交给另一个线程。在这种情况下,上面的故事发生在另一个线程中,协程调度程序还管理延续对象,以便在返回值可用时恢复它。

我发现理解挂起的最好方法是在这个关键字和coroutineContext属性之间做一个类比。

Kotlin函数可以声明为本地函数或全局函数。局部函数可以神奇地访问这个关键字,而全局函数不能。

Kotlin函数可以声明为挂起或阻塞。挂起函数可以神奇地访问coroutineContext属性,而阻塞函数不能。

它是:coroutineContext属性 在Kotlin stdlib中像“正常”属性一样声明,但此声明只是用于文档/导航目的的存根。事实上,coroutineContext是一个内置的内在属性,这意味着在编译器的魔法下,它知道这个属性,就像它知道语言关键字一样。

这个关键字对局部函数的作用与coroutineContext属性对挂起函数的作用相同:它提供了对当前执行上下文的访问。

因此,您需要挂起以获得对coroutineContext属性的访问—当前执行的协程上下文的实例

对于那些仍然想知道如何挂起一个挂起函数的人,我们在挂起函数体中使用了suspendCoroutine函数。

    suspend fun foo() :Int
  {
    Log.d(TAG,"Starting suspension")
    return suspendCoroutine<Int> { num->

      val result = bar()
      Log.d(TAG,"Starting resumption")           
      num.resumeWith(Result.success(result))
    }

  }

fun bar():Int //this is a long runnning task

挂起函数是所有协程的中心。 挂起函数只是一个可以在以后暂停和恢复的函数。它们可以执行一个长时间运行的操作,并等待它完成而不阻塞。

暂停函数的语法与常规函数相似,只是增加了suspend关键字。它可以接受一个参数并具有返回类型。但是,挂起函数只能由另一个挂起函数或在协程中调用。

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

在底层,挂起函数由编译器转换为另一个不带suspend关键字的函数,该函数接受一个类型为Continuation<T>的附加形参。例如,上面的函数将被编译器转换为:

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

Continuation<T>是一个包含两个函数的接口,如果函数挂起时发生错误,则调用这两个函数以返回值或异常恢复协程。

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}