onm*_*133 57 suspend async-await kotlin kotlin-coroutines
我正在读Kotlin Coroutine并知道它是基于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()
...
}
Run Code Online (Sandbox Code Playgroud)
它说"在完成某些计算之前暂停协程",但协同程序就像一个轻量级的线程.因此,如果协程被暂停,那么计算怎么办呢?
我们看到await被调用computation,所以它可能会async返回Deferred,这意味着它可以启动另一个协同程序
fun computation(): Deferred<Boolean> {
return async {
true
}
}
Run Code Online (Sandbox Code Playgroud)
引述说暂停协程.它是指suspend外部的asynccoroutine,还是suspend内部的computationcoroutine?
这suspend意味着当外部async协程正在wait(await)为内部computation协程完成时,它(外部async协程)空闲(因此名称暂停)并将线程返回到线程池,当子computation协程完成时,它(外部async协同程序) )醒来,从池中取出另一个线程并继续?
我提到这个主题的原因是因为https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
当协程正在等待时,线程返回到池中,当等待完成时,协程将在池中的空闲线程上恢复
Sof*_*uni 40
暂停功能是所有协同程序的核心.暂停功能只是一个可以暂停并在以后恢复的功能.他们可以执行长时间运行的操作并等待它完成而不会阻塞.
除了添加suspend关键字之外,挂起函数的语法类似于常规函数的语法.它可以采用参数并具有返回类型.但是,挂起函数只能由另一个挂起函数或协程调用.
suspend fun backgroundTask(param: Int): Int {
// long running operation
}
Run Code Online (Sandbox Code Playgroud)
在引擎盖下,挂起函数由编译器转换为另一个没有suspend关键字的函数,该函数采用Continuation类型的附加参数.例如,上面的函数将由编译器转换为:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
// long running operation
}
Run Code Online (Sandbox Code Playgroud)
Continuation是一个接口,它包含两个函数,这些函数被调用以恢复具有返回值的协程,或者如果在函数被挂起时发生错误则具有异常.
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Run Code Online (Sandbox Code Playgroud)
Kus*_*hal 20
由于已经有很多好的答案,我想为其他人发布一个更简单的示例。
runBlocking用例:
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);
}
}
Run Code Online (Sandbox Code Playgroud)这输出:
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
Run Code Online (Sandbox Code Playgroud)
启动用例:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)这输出:
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
Run Code Online (Sandbox Code Playgroud)
异步和等待用例:
async并且await会有所帮助。2挂起函数 myMethod() 和 myMethod2()。myMethod2()应仅在myMethod() OR 完全完成后执行myMethod2()取决于 的结果myMethod(),我们可以使用async和awaitasync并行启动一个协程,类似于launch. 但是,它提供了一种在并行启动另一个协程之前等待一个协程的方法。那种方式是await()。async返回 的一个实例Deffered<T>。T将是Unit默认的。当我们需要等待所有async的完成,我们需要调用.await()上Deffered<T>的该实例async。就像在下面的例子中,我们调用innerAsync.await()这意味着执行将被暂停,直到innerAsync完成。我们可以在输出中观察到相同的情况。首先innerAsync完成,它调用myMethod(). 然后下一步async innerAsync2开始,它调用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);
}
}
}
Run Code Online (Sandbox Code Playgroud)这输出:
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
Run Code Online (Sandbox Code Playgroud)
Mar*_*nik 16
作为一种学习工具,我建议你仔细阅读这段代码,它揭示了所有便利构造的基本机制,例如Unconfined:
import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
var continuation: Continuation<Int>? = null
fun main() = runBlocking {
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
该launch协程调度基本上消除了协程调度的魔力:里面的代码launch块只是开始作为的一部分执行val a = a()的呼叫.会发生什么如下:
b()suspendCoroutine到达b().suspendCoroutine执行传递给的块COROUTINE_SUSPENDED,然后返回一个特殊a()值.这个值不能通过Kotlin编程模型观察到,但这就是编译后的Java方法所做的.launch看到这个返回值的函数本身也会返回它.launch块执行相同的操作,控件现在返回到10.downTo(0)...调用后的行:launch请注意,此时,您具有与fun main块内的launch代码和代码同时执行的效果相同的效果.只是发生所有这一切都发生在一个本机线程上,所以forEach块被"暂停".
现在,内部continuation循环的代码,程序读取b()的resumes功能,写和10它的价值resume().suspendCoroutine以这样一种方式实现,就好像b()调用与你传入的值一起返回.所以你突然发现自己处于执行的中间resume().传递给的值i被分配给0并检查while (true).如果它不为零,则b()循环在内部继续suspendCoroutine,再次到达resume(),此时您的forEach()调用返回,现在您将进入另一个循环步骤0.这一直持续到最后你继续println,然后launch语句运行并且程序完成.
上面的分析应该给你一个重要的直觉,即"暂停协程"意味着将控制权返回到最内部的resume()调用(或者更一般地说,是协同程序构建器).如果协程在恢复后再次挂起,则resume()呼叫结束并且控制返回到呼叫者continuation.
协同调度程序的存在使得这种推理不那么明确,因为大多数会立即将您的代码提交给另一个线程.在这种情况下,上述故事发生在其他线程中,协程调度程序也管理Unconfined对象,以便在返回值可用时恢复它.
我想给你举一个关于延续概念的简单例子。这就是挂起函数的作用,它可以冻结/挂起,然后继续/恢复。不要在线程和信号量方面考虑协程。从延续甚至回调钩子的角度考虑它。
需要明确的是,可以使用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")
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码打印以下内容:
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
Run Code Online (Sandbox Code Playgroud)
想象它是这样工作的:
因此,您从中启动的当前函数不会停止,只是协程在继续时会暂停。线程不会通过运行挂起函数而暂停。
我认为这个网站可以帮助你解决问题,是我的参考。
让我们做一些很酷的事情并在迭代过程中冻结我们的挂起函数。我们将在稍后恢复它onResume
存储一个名为的变量continuation,我们将为我们加载协程延续对象:
var continuation: CancellableContinuation<String>? = null
suspend fun freezeHere() = suspendCancellableCoroutine<String> {
continuation = it
}
fun unFreeze() {
continuation?.resume("im resuming") {}
}
Run Code Online (Sandbox Code Playgroud)
现在,让我们回到我们的暂停函数,让它在迭代中间冻结:
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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后在 onResume 中的其他地方(例如):
override fun onResume() {
super.onResume()
unFreeze()
}
Run Code Online (Sandbox Code Playgroud)
循环将继续。知道我们可以随时冻结挂起函数并在 beeb 过去一段时间后恢复它,这真是太好了。您还可以查看频道
协程或功能被暂停?
调用暂停荷兰国际集团功能暂停小号的协程,意味着当前线程可以开始执行另一个协程。因此,协程据说是暂停的,而不是功能。
但是从技术上讲,此时您的函数将不会由另一个协程执行,因此可以说该函数和协程都停止了,但是我们在这里进行了分头。
哪个协程被暂停?
外部async启动协程。当它调用时computation(),内部将async启动第二个协程。然后,对的调用会await()中止外部 async协程的执行,直到内部 async协程的执行结束。
您甚至可以看到一个线程:该线程将执行外部线程async的开始,然后调用computation()并到达内部线程async。在这一点上,内部异步的主体被跳过,线程继续执行外部异步async直到到达await()。
await()是“悬浮点”,因为await是悬浮功能。这意味着外部协程已暂停,因此线程开始执行内部协程。完成后,返回执行外部的结尾async。
是否挂起表示外部异步协程正在等待(等待)内部计算协程完成时,它(外部异步协程)空闲(因此名称为suspend)并将线程返回到线程池,以及子计算协程完成时,它(外部异步协程)唤醒,从池中获取另一个线程,然后继续吗?
是的,正好。
我发现最好的理解方法suspend是在this关键字和coroutineContext属性之间进行类比。
Kotlin函数可以声明为局部或全局。局部函数神奇地可以访问this关键字,而全局函数则不能。
Kotlin函数可以声明为suspend或阻塞。suspend函数神奇地可以访问coroutineContext属性,而阻止函数则不能。
问题是:coroutineContext属性
在Kotlin stdlib中像“普通”属性一样被声明,但是此声明只是出于文档/导航目的的存根。实际上coroutineContext是内置的内在属性,这意味着在编译器的内部魔术师意识到此属性,就像它意识到语言关键字一样。
哪些this关键字确实为当地的功能是什么coroutineContext属性确实为suspend功能:它可以访问到执行的当前上下文。
因此,您需要suspend访问coroutineContext属性-当前执行的协程上下文的实例
这里有很多很好的答案,但我认为还有两件事需要注意。
launch / withContext / runBlocking 以及示例中的许多其他内容都来自协程库。这实际上与挂起无关。您不需要协程库即可使用协程。协程是一个编译器“技巧”。是的,这个库确实让事情变得更容易,但是编译器正在发挥暂停和恢复事情的魔力。
第二件事是,编译器只是将看起来是程序化的代码转化为底层的回调。
采用以下不使用协程库的挂起最小协程:
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)
Run Code Online (Sandbox Code Playgroud)
我认为理解它的一个重要方法是查看编译器对这段代码做了什么。它实际上为 lambda 创建了一个类。它在类中为“额外”字符串创建一个属性,然后创建两个函数,一个打印“之前”,另一个打印“之后”。
实际上,编译器采用了看起来像程序代码的内容并将其转换为回调。
那么suspend关键字有什么作用呢?它告诉编译器要回溯多远来查找生成的回调需要的上下文。编译器需要知道哪些变量在哪些“回调”中使用,而 suspend 关键字可以帮助它。在此示例中,“extra”变量在挂起之前和之后都使用。因此需要将其拉出到包含编译器进行的回调的类的属性。
它还告诉编译器这是状态的“开始”并准备将以下代码拆分为回调。唯一startCoroutine存在于挂起 lambda 上。
Kotlin 编译器生成的实际 Java 代码在这里。这是一个 switch 语句而不是回调,但实际上是同一件事。首先调用案例 0,然后在恢复后调用案例 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");
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13894 次 |
| 最近记录: |