Kotlin协程中的suspend函数是什么意思?

vuv7lop3  于 2023-03-30  发布在  Kotlin
关注(0)|答案(9)|浏览(190)

我正在阅读KotlinCoroutine,知道它是基于suspend函数的。但是suspend是什么意思?

协程或函数挂起?

https://kotlinlang.org/docs/reference/coroutines.html开始
基本上,协程是可以挂起而不会阻塞线程的计算
我常听人说“暂停函数”。但我认为是协程被暂停,因为它在等待函数完成?“暂停”通常意味着“停止操作”,在这种情况下协程是空闲的。
我们应该说协程被暂停了吗?

哪个协程被挂起

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是在computation上调用的,所以可能是async返回Deferred,这意味着它可以启动另一个协程

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

引用中的***表示挂起协程***。它是指suspend外部async协程,还是suspend内部computation协程?
suspend是否意味着外部async协程正在等待(await)为内部computation协程完成,它(外部async协程)空闲(因此命名为suspend)并将thread返回到线程池,当子computation协程完成时,它将返回线程池。(外部async协程)唤醒,从池中取出另一个线程并继续?
我提到这个线程的原因是因为https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
线程在协程等待时返回到池中,等待完成后,协程在池中的一个空闲线程上恢复

vsdwdz23

vsdwdz231#

suspend是什么意思
suspend关键字标记的函数在编译时被转换为异步(在字节码中),即使它们在源代码中显示为同步。
了解这种转变IMO的最佳来源是Roman Elizarov的谈话"Deep Dive into Coroutines"
例如,此函数:

class MyClass {
    suspend fun myFunction(arg: Int): String {
        delay(100)
        return "bob"
    }
}

转换为(为了简单起见,用Java而不是实际的JVM字节码表示):

public final class MyClass {
    public final Object myFunction(int arg, @NotNull Continuation<? super String> $completion) {
        // body turned into a state machine, hidden for brevity
    }
}

这包括对函数的以下更改:

  • 返回类型更改为Java的Object(相当于Kotlin的Any?-包含所有值的类型),以允许返回一个特殊的COROUTINE_SUSPENDED令牌来表示协程何时实际挂起
  • 它获取了一个额外的Continuation<X>参数(其中X是代码中声明的函数的前一个返回类型-在示例中它是String)。
  • 它的函数体被转换成一个状态机(而不是字面上使用回调,以提高效率)。这是通过将函数体分解成所谓的“挂起点”周围的部分,并将这些部分转换成一个大开关的分支来完成的。关于局部变量的状态以及我们在开关中的位置存储在Continuation对象中。

这是一种非常快速的描述方式,但您可以在演讲中看到更多细节和示例。整个转换基本上是如何实现“挂起/恢复”机制的。
协程或函数被挂起?
在高层,我们说调用suspending函数suspendsthe coroutine,意思是当前线程可以开始执行另一个协程,所以,被挂起的是 coroutine 而不是函数。
实际上,挂起函数的调用位置因此被称为“挂起点”。
哪个协程被挂起?
让我们看看你的代码,并分解发生了什么(编号遵循执行时间轴):

// 1. this call starts a new coroutine (let's call it C1).
//    If there were code after it, it would be executed concurrently with
//    the body of this async
async {
    ...
    // 2. this is a regular function call, so we go to computation()'s body
    val deferred = computation()
    // 4. we're back from the call to computation, about to call await()
    //    Because await() is suspendING, it suspends coroutine C1.
    //    This means that if we had a single thread in our dispatcher, 
    //    it would now be free to go execute C2. With multiple threads,
    //    C2 may have already started executing. In any case we wait 
    //    here for C2 to complete.
    // 7. once C2 completes, C1 is resumed with the result `true` of C2's async
    val result = deferred.await() 
    ...
    // 8. C1 can now keep going in the current thread until it gets 
    //    suspended again (or not)
}

fun computation(): Deferred<Boolean> {
    // 3. this async call starts a second coroutine (C2). Depending on the 
    //    dispatcher you're using, you may have one or more threads.
    // 3.a. If you have multiple threads, the block of this async could be
    //      executed in parallel of C1 in another thread
    // 3.b. If you have only one thread, the block is sort of "queued" but 
    //      not executed right away (as in an event loop)
    //
    //    In both cases, we say that this block executes "concurrently"
    //    with C1, and computation() immediately returns the Deferred
    //    instance to its caller (unless a special dispatcher or 
    //    coroutine start argument is used, but let's keep it simple).
    return async {
        // 5. this may now be executed
        true
        // 6. C2 is now completed, so the thread can go back to executing 
        //    another coroutine (e.g. C1 here)
    }
}

外部async启动一个协程。当它调用computation()时,内部async启动第二个协程。然后,对await()的调用挂起 * 外部 * async协程的执行,直到 * 内部 * async的协程执行结束。
你甚至可以用一个线程看到:线程将执行外部async的开始,然后调用computation()并到达内部async。此时,跳过内部async的主体,并且线程继续执行外部async直到其到达await()await()是“挂起点”,因为await是一个挂起函数。这意味着外部协程被挂起,因此线程开始执行内部协程。当它完成时,它会返回执行外部async的末尾。
suspend是否意味着当外部异步协程等待(await)内部计算协程完成时,它(外部异步协程)空闲(因此得名suspend)并将线程返回到线程池,当子计算协程完成时,它(外部异步协程)唤醒,从池中取出另一个线程并继续?
是的,没错。
实际上实现的方法是将每个挂起函数转换为一个状态机,其中每个“状态”对应于该挂起函数中的一个挂起点。在后台,该函数可以被多次调用,并提供有关它应该从哪个挂起点开始执行的信息(您应该观看我链接的视频以了解更多信息)。

relj7zay

relj7zay2#

要理解挂起协程的确切含义,我建议您查看以下代码:

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

Unconfined协程调度器消除了 * 协程调度 * 的魔力,并允许我们直接关注裸协程。
作为launch调用的一部分,launch块中的代码立即开始在当前线程上执行。发生的情况如下:
1.评估val a = a()
1.这链接到b(),达到suspendCoroutine
1.函数b()执行传递给suspendCoroutine的块,然后返回一个特殊的COROUTINE_SUSPENDED值。这个值在Kotlin编程模型中是不可观察的,但编译后的Java方法就是这样做的。
1.函数a()看到这个返回值,它本身也返回它。

  1. launch块也是如此,现在控制返回到launch调用之后的行:10.downTo(0)...
    请注意,此时,launch块中的代码和fun main代码并发执行的效果相同。只是所有这些都发生在单个本机线程上,因此launch块被“挂起”。
    现在,在forEach循环代码中,程序读取b()函数写入的continuation,并将resumes的值写入10resume()的实现方式是,就好像suspendCoroutine调用返回的是您传入的值。因此,您突然发现自己正在执行b()。您传递给resume()的值被分配给i,并根据0进行检查。如果它不为零,则while (true)循环将在b()内部继续。再次到达suspendCoroutine,此时您的resume()调用返回,现在您在forEach()中执行另一个循环步骤。这一直持续到最后您使用0继续执行,然后println语句运行,程序完成。
    上面的分析应该给予你一个重要的直觉,即“挂起一个协程”意味着将控制返回到最里面的launch调用(或者更一般地说,* 协程构建器 *)。如果协程在恢复后再次挂起,则resume()调用结束,控制返回到resume()的调用者。
    协程调度器的存在使得这个推理不那么明确,因为大多数协程调度器会立即将您的代码提交给另一个线程。在这种情况下,上述故事发生在另一个线程中,协程调度器也管理continuation对象,以便在返回值可用时恢复它。
zvokhttg

zvokhttg3#

因为已经有很多好的答案了,我想为其他人发布一个更简单的例子。

runBlocking用例:

  • myMethod()是suspend函数
  • runBlocking { }以阻塞的方式启动一个协程。这类似于我们如何使用Thread类阻塞正常线程,并在某些事件发生后通知阻塞的线程。
  • runBlocking { }阻塞当前正在执行的线程,直到协程({}之间的主体)完成
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 { }同时启动一个协程。
  • 这意味着当我们指定launch时,协程在worker线程上开始执行。
  • worker线程和外部线程(我们称之为launch { })都是并发运行的。JVM内部可能会执行抢占式线程
  • 当我们需要多个任务并行运行时,我们可以使用这个。有scopes指定了协程的生存期。如果我们指定GlobalScope,协程将一直工作到应用程序的生存期结束。
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

asyncawait用例:

  • 当我们有多个任务要做并且它们依赖于其他人的完成时,asyncawait会有所帮助。
  • 例如,在下面的代码中,有2挂起函数myMethod()和myMethod2()。myMethod2()应该只有在myMethod()完全完成后才执行myMethod2()取决于myMethod()的结果,我们可以使用asyncawait
  • async以类似于launch的方式并行启动协程。但是,它提供了一种在并行启动另一个协程之前等待一个协程的方法。
  • await() . async返回Deffered<T>的示例. T默认为Unit.当我们需要等待任何async完成时,我们需要在asyncDeffered<T>示例上调用.await().如下例所示,我们调用innerAsync.await(),这意味着执行将被挂起,直到innerAsync完成。我们可以在输出中观察到相同的情况。innerAsync首先完成,这调用myMethod()。然后asyncinnerAsync2启动,这调用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
093gszye

093gszye4#

我发现理解suspend的最好方法是在this关键字和coroutineContext属性之间进行类比。
Kotlin函数可以声明为local或global。Local函数神奇地访问了this关键字,而global函数却没有。
Kotlin函数可以声明为suspend或blocking。suspend函数神奇地访问coroutineContext属性,而blocking函数则不能。
问题是:coroutineContext属性在Kotlinstdlib中被声明为一个“普通”属性,但这个声明只是一个用于文档/导航目的的存根。实际上,coroutineContext是内置的固有属性,这意味着编译器可以像知道语言关键字一样知道这个属性。
this关键字对本地函数的作用与coroutineContext属性对suspend函数的作用相同:它提供对当前执行上下文的访问。

因此,您需要suspend来访问coroutineContext属性-当前执行的协程上下文的示例

46qrfjad

46qrfjad5#

我想给予你一个关于continuation概念的简单例子。这就是suspend函数的作用,它可以冻结/挂起,然后继续/恢复。不要再从线程和信号量的Angular 考虑协程,而是从continuation甚至回调钩子的Angular 考虑协程。
为了清楚起见,协程可以通过使用suspend函数来暂停。让我们研究一下:
在Android中,我们可以这样做:

var TAG = "myTAG:"
        fun myMethod() { // function A in image
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                    }
            }

            //this area is not suspended, you can continue doing work
        }

        suspend fun freezePleaseIAmDoingHeavyWork() { // function B in image
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

上面的代码将打印以下内容:

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

想象它是这样工作的:

所以你启动的当前函数不会停止,只是协程在继续的时候会挂起。线程不会因为运行挂起函数而暂停。
我想你把事情说清楚,是我的参考。
让我们做一些很酷的事情,在迭代的中间冻结suspend函数。
存储一个名为continuation的变量,我们将用协程continuation对象加载它:

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze() {
            continuation?.resume("im resuming") {}
        }

现在,让我们返回到挂起的函数,并使其在迭代过程中冻结:

suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }
            }
        }
    }

然后在其他地方像onResume(例如):

override fun onResume() {
        super.onResume()
        unFreeze()
    }

循环将继续。知道我们可以在任何时候冻结一个suspend函数并在beeb经过一段时间后恢复它,这是非常简洁的。

nzrxty8p

nzrxty8p6#

这里有很多很好的答案,但我认为还有两件事值得注意。
launch / withContext / runBlocking和例子中的很多其他东西都来自于协程库。这实际上与suspend无关。你不需要协程库来使用协程。协程是编译器的一个“技巧”。是的,库确实让事情变得更容易,但是编译器正在做暂停和恢复事情的魔术。
第二件事是,编译器只是将看起来像过程的代码转换为后台的回调。
以下面这个挂起的不使用协程库的最小协程为例:

lateinit var context: Continuation<Unit>

    suspend {
        val extra="extra"
        println("before suspend $extra")
        suspendCoroutine<Unit> { context = it }
        println("after suspend $extra")
    }.startCoroutine(
        object : Continuation<Unit> {
            override val context: CoroutineContext = EmptyCoroutineContext
            // called when a coroutine ends. do nothing.
            override fun resumeWith(result: Result<Unit>) {
                result.onFailure { ex : Throwable -> throw ex }
            }
        }
    )

    println("kick it")
    context.resume(Unit)

我认为理解它的一个重要方法是看看编译器对这段代码做了什么。有效地,它为lambda创建了一个类。它为“额外”字符串在类中创建了一个属性,然后它创建了两个函数,一个打印“before”,另一个打印“after”。
实际上,编译器将看起来像过程代码的代码转换为回调。
那么suspend关键字是做什么的呢?它告诉编译器在生成的回调中需要多久才能找到上下文。编译器需要知道哪些变量在哪些“回调”中使用,suspend关键字帮助了它。在这个例子中,“extra”变量在suspend之前和之后都使用。因此,需要将其提取到包含编译器所做回调的类的属性中。
它还告诉编译器这是状态的“开始”,并准备将以下代码分割为回调。startCoroutine只存在于suspend lambda中。
Kotlin编译器生成的实际Java代码在这里。它是一个switch语句而不是回调,但实际上是一样的。首先调用w/ case 0,然后在resume之后调用w/ case 1。

@Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
                var10_2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                switch (this.label) {
                    case 0: {
                        ResultKt.throwOnFailure((Object)$result);
                        extra = "extra";
                        var3_4 = "before delay " + extra;
                        var4_9 = false;
                        System.out.println((Object)var3_4);
                        var3_5 = this;
                        var4_9 = false;
                        var5_10 = false;
                        this.L$0 = extra;
                        this.L$1 = var3_5;
                        this.label = 1;
                        var5_11 = var3_5;
                        var6_12 = false;
                        var7_13 = new SafeContinuation(IntrinsicsKt.intercepted((Continuation)var5_11));
                        it = (Continuation)var7_13;
                        $i$a$-suspendCoroutine-AppKt$main$1$1 = false;
                        this.$context.element = it;
                        v0 = var7_13.getOrThrow();
                        if (v0 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
                            DebugProbesKt.probeCoroutineSuspended((Continuation)var3_5);
                        }
                        v1 = v0;
                        if (v0 == var10_2) {
                            return var10_2;
                        }
                        ** GOTO lbl33
                    }
                    case 1: {
                        var3_6 = this.L$1;
                        extra = (String)this.L$0;
                        ResultKt.throwOnFailure((Object)$result);
                        v1 = $result;
lbl33:
                        // 2 sources

                        var3_8 = "after suspend " + extra;
                        var4_9 = false;
                        System.out.println((Object)var3_8);
                        return Unit.INSTANCE;
                    }
                }
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
w46czmvw

w46czmvw7#

假设我们有一个名为myFunction的函数。

fun myFunction(){
Code block 1
Code block 2 //this one has a long running operation
Code block 3
Code block 4
}

通常,这些代码块的执行方式类似于block1、block2、block3、block4。因此,代码块3和4可能会在代码块2仍在运行时执行。由于这个原因,可能会出现问题。(屏幕可能冻结,应用程序可能会崩溃)
但是如果我们让这个函数暂停

suspend fun MyFunction(){
Code block 1
Code block 2 //this one has a long running operation
Code block 3
Code block 4
}

现在,这个函数可以在代码块2(长时间运行的操作)开始执行时暂停,并在完成后恢复。代码块3和4将在此之后执行。因此不会出现意外的线程共享问题。

dgenwo3n

dgenwo3n8#

对于那些仍然想知道如何实际挂起suspend函数的人,我们在suspend函数的主体中使用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
cfh9epnr

cfh9epnr9#

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

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

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

在底层,suspend函数被编译器转换为另一个没有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)
}

相关问题