Kotlin挂起函数应用介绍

 更新时间:2022年11月22日 10:20:32   作者:且听真言  
挂起函数用状态机以挂起点将协程的运算逻辑拆分成不同的片段,每次执行协程运行不同的逻辑片段,由此可以知道协程是运行在线程中的,线程的并发处理方式也可以用在协程上

学习了极客时间课程,记录下学习输出。

一、CPS转换

挂起函数,比普通的函数多了 suspend 关键字。通过suspend 关键字,Kotlin 编译器就会特殊对待这个函数,将其转换成一个带有 Callback 的函数,这里的 Callback 就是 Continuation 接口。

例、CPS 转换:

suspend fun getUserInfo(): Any {
    return "UserInfo"
}
----->
fun getUserInfo(ct:Continuation): Any? {
    ct.resumeWith("UserInfo")
    return Unit
}

PS 转换过程中,函数的类型发生了变化:suspend ()->Any 变成了 (Continuation)-> Any?。这意味着,如果你在 Java 里访问一个 Kotlin 挂起函数 getUserInfo(),会看到 getUserInfo() 的类型是 (Continuation)-> Object,接收 Continuation 为参数,返回值是 Object。而在这里,函数签名的变化可以分为两个部分:函数签名的变化可以分为两个部分:函数参数的变化和函数返回值的变化。

1.CPS 参数变化

suspend() 变成 (Continuation)

suspend fun getUserInfoContent(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "UserInfo"
}
suspend fun getFriendListContent(user: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "Friend1, Friend2"
}
suspend fun getFeedListContent(user: String, list: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "{FeddList...}"
}
suspend fun fetchContent() {
    val userInfoContent = getUserInfoContent()
    val friendListContent = getFriendListContent(userInfoContent)
    val feedListContent = getFeedListContent(userInfoContent, friendListContent)
}

上述代码转换成java代码如下:

public final class TestCoroutionKt {
   @Nullable
   public static final Object getUserInfoContent(@NotNull Continuation var0) {
      Object $continuation;
      label20: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.getUserInfoContent(this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
         Function2 var10001 = (Function2)(new Function2((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  this.label = 1;
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
               return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkNotNullParameter(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               return var3;
            }
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         });
         ((<undefinedtype>)$continuation).label = 1;
         if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var3) {
            return var3;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "UserInfo";
   }
   @Nullable
   public static final Object getFriendListContent(@NotNull String var0, @NotNull Continuation var1) {
      Object $continuation;
      label20: {
         if (var1 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var1;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var1) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.getFriendListContent((String)null, this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
         Function2 var10001 = (Function2)(new Function2((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  this.label = 1;
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
               return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkNotNullParameter(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               return var3;
            }
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         });
         ((<undefinedtype>)$continuation).label = 1;
         if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var4) {
            return var4;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "Friend1, Friend2";
   }
   @Nullable
   public static final Object getFeedListContent(@NotNull String var0, @NotNull String var1, @NotNull Continuation var2) {
      Object $continuation;
      label20: {
         if (var2 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var2;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         $continuation = new ContinuationImpl(var2) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.getFeedListContent((String)null, (String)null, this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
         Function2 var10001 = (Function2)(new Function2((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  this.label = 1;
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
               return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkNotNullParameter(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               return var3;
            }
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         });
         ((<undefinedtype>)$continuation).label = 1;
         if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var5) {
            return var5;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "{FeddList...}";
   }
   @Nullable
   public static final Object fetchContent(@NotNull Continuation var0) {
      Object $continuation;
      label37: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.fetchContent(this);
            }
         };
      }
      Object var10000;
      label31: {
         String userInfoContent;
         Object var6;
         label30: {
            Object $result = ((<undefinedtype>)$continuation).result;
            var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(((<undefinedtype>)$continuation).label) {
            case 0:
               ResultKt.throwOnFailure($result);
               ((<undefinedtype>)$continuation).label = 1;
               var10000 = getUserInfoContent((Continuation)$continuation);
               if (var10000 == var6) {
                  return var6;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            case 2:
               userInfoContent = (String)((<undefinedtype>)$continuation).L$0;
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label30;
            case 3:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label31;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            userInfoContent = (String)var10000;
            ((<undefinedtype>)$continuation).L$0 = userInfoContent;
            ((<undefinedtype>)$continuation).label = 2;
            var10000 = getFriendListContent(userInfoContent, (Continuation)$continuation);
            if (var10000 == var6) {
               return var6;
            }
         }
         String friendListContent = (String)var10000;
         ((<undefinedtype>)$continuation).L$0 = null;
         ((<undefinedtype>)$continuation).label = 3;
         var10000 = getFeedListContent(userInfoContent, friendListContent, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
      }
      String var3 = (String)var10000;
      return Unit.INSTANCE;
   }
}

每一次函数调用的时候,continuation 都会作为最后一个参数传到挂起函数里,Kotlin 编译器帮我们做的,我们开发者是无感知。

2.CPS 返回值变化

final Object getUserInfoContent(@NotNull Continuation var0)
final Object getFriendListContent(@NotNull String var0, @NotNull Continuation var1)
final Object getFeedListContent(@NotNull String var0, @NotNull String var1, @NotNull Continuation var2)
suspend fun getUserInfoContent(): String {}
fun getUserInfoContent(cont: Continuation): Any? {}

经过 CPS 转换后,完整的函数签名如下:

suspend fun getUserInfoContent(): String {}
fun getUserInfoContent(cont: Continuation<String>): Any? {}

Kotlin 编译器的 CPS 转换是等价的转换。suspend () -> String 转换成 (Continuation) -> Any?。

挂起函数经过 CPS 转换后,它的返回值有一个重要作用:标志该挂起函数有没有被挂起。

其实挂起函数也能不被挂起。

首先只要有 suspend 修饰的函数,它就是挂起函数。

suspend fun getUserInfoContent(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "UserInfo"
}

执行到 withContext{} 的时候,就会返回 CoroutineSingletons.COROUTINE_SUSPENDED 表示函数被挂起了。

下面的函数则是伪挂起函数

suspend fun getUserInfoContent2(): String {
    return "UserInfo"
}

因为它的方法体跟普通函数一样。它跟一般的挂起函数有个区别:在执行的时候,它并不会被挂起,因为它就是个普通函数。

二、挂起函数的反编译

 @Nullable
   public static final Object fetchContent(@NotNull Continuation var0) {
      Object $continuation;
      label37: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.fetchContent(this);
            }
         };
      }
      Object var10000;
      label31: {
         String userInfoContent;
         Object var6;
         label30: {
            Object $result = ((<undefinedtype>)$continuation).result;
            var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(((<undefinedtype>)$continuation).label) {
            case 0:
               ResultKt.throwOnFailure($result);
               ((<undefinedtype>)$continuation).label = 1;
               var10000 = getUserInfoContent((Continuation)$continuation);
               if (var10000 == var6) {
                  return var6;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            case 2:
               userInfoContent = (String)((<undefinedtype>)$continuation).L$0;
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label30;
            case 3:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label31;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            userInfoContent = (String)var10000;
            ((<undefinedtype>)$continuation).L$0 = userInfoContent;
            ((<undefinedtype>)$continuation).label = 2;
            var10000 = getFriendListContent(userInfoContent, (Continuation)$continuation);
            if (var10000 == var6) {
               return var6;
            }
         }
         String friendListContent = (String)var10000;
         ((<undefinedtype>)$continuation).L$0 = null;
         ((<undefinedtype>)$continuation).label = 3;
         var10000 = getFeedListContent(userInfoContent, friendListContent, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
      }
      String var3 = (String)var10000;
      return Unit.INSTANCE;
   }

label 是用来代表协程状态机当中状态;

result 是用来存储当前挂起函数执行结果;

invokeSuspend 这个函数,是整个状态机的入口,它会将执行流程转交给 fetchContent 进行再次调用;

userInfoContent, friendListContent用来存储历史挂起函数执行结果。

if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }
  $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.fetchContent(this);
            }
         };

invokeSuspend 最终会调用 fetchContent;

如果是初次运行,会创建一个 ContinuationImpl对象,completion 作为参数;这相当于用一个新的 Continuation 包装了旧的 Continuation;

如果不是初次运行,直接将 completion 赋值给 continuation;这说明 continuation 在整个运行期间,只会产生一个实例,这能极大地节省内存开销(对比 CallBack)。

// result 接收协程的运行结果
var result = continuation.result
// suspendReturn 接收挂起函数的返回值
var suspendReturn: Any? = null
// CoroutineSingletons 是个枚举类
// COROUTINE_SUSPENDED 代表当前函数被挂起了
val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED

continuation.label 是状态流转的关键,continuation.label 改变一次,就代表了挂起函数被调用了一次;每次挂起函数执行完后,都会检查是否发生异常;

fetchContent 里的原本的代码,被拆分到状态机里各个状态中,分开执行;getUserInfoContent(continuation)、getFriendListContent(user, continuation)、getFeedListContent(friendList, continuation) 三个函数调用的是同一个 continuation 实例;

var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();

如果一个函数被挂起了,它的返回值会是 CoroutineSingletons.COROUTINE_SUSPENDED;

在挂起函数执行的过程中,状态机会把之前的结果以成员变量的方式保存在 continuation 中。

本质上来说,Kotlin 协程就是通过 label 代码段嵌套,配合 switch 巧妙构造出一个状态机结构。

三、Continuation

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}
@Suppress("WRONG_MODIFIER_TARGET")
public suspend inline val coroutineContext: CoroutineContext
    get() {
        throw NotImplementedError("Implemented as intrinsic")
    }

注意上面的suspend inline val coroutineContext,suspend 的这种用法只是一种特殊用法。它的作用:它是一个只有在挂起函数作用域下,才能访问的顶层的不可变的变量。这里的 inline,意味着它的具体实现会被直接复制到代码的调用处。

suspend fun testContext() = coroutineContext
@Nullable
   public static final Object testContext(@NotNull Continuation $completion) {
      return $completion.getContext();
   }

“suspend inline val coroutineContext”,本质上就是 Kotlin 官方提供的一种方便开发者在挂起函数当中,获取协程上下文的手段。它的具体实现,其实是 Kotlin 编译器来完成的。

我们在挂起函数当中无法直接访问 Continuation 对象,但可以访问到 Continuation 当中的 coroutineContext。要知道,正常情况下,我们想要访问 Continuation.coroutineContext,首先是要拿到 Continuation 对象的。但是,Kotlin 官方通过“suspend inline val coroutineContext”这个顶层变量,让我们开发者能直接拿到 coroutineContext,却对 Continuation 毫无感知。

挂起函数与 CoroutineContext 确实有着紧密的联系。每个挂起函数当中都会有 Continuation,而每个 Continuation 当中都会有 coroutineContext。并且,我们在挂起函数当中,就可以直接访问当前的 coroutineContext。

到此这篇关于Kotlin挂起函数应用介绍的文章就介绍到这了,更多相关Kotlin挂起函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Android 中API之Drawable资源详解及简单实例

    Android 中API之Drawable资源详解及简单实例

    这篇文章主要介绍了Android 中API之Drawable资源详解及简单实例的相关资料,需要的朋友可以参考下
    2017-05-05
  • Android 自定义TextView实现滑动解锁高亮文字

    Android 自定义TextView实现滑动解锁高亮文字

    这篇文章主要介绍了Android 自定义TextView实现滑动解锁高亮文字的相关资料,需要的朋友可以参考下
    2018-03-03
  • dagger2使用方法教程之简明讲解

    dagger2使用方法教程之简明讲解

     Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。下面这篇文章主要给大家介绍了关于dagger2的一些使用方法的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下。
    2018-04-04
  • flutter 输入框组件TextField的实现代码

    flutter 输入框组件TextField的实现代码

    这篇文章主要介绍了flutter 输入框组件TextField的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-07-07
  • Android 模拟器(emulator-5554...)出现错误解决办法

    Android 模拟器(emulator-5554...)出现错误解决办法

    这篇文章主要介绍了Android 模拟器出现错误解决办法的相关资料,如:Unable to get view server version from device,Failed to install helloworld.apk on device 'emulator-5554': timeout,这种常见错误,解决办法,需要的朋友可以参考下
    2016-11-11
  • Android 日期选择器实例代码

    Android 日期选择器实例代码

    这篇文章主要介绍了Android 日期选择器实例代码,需要的朋友可以参考下
    2017-05-05
  • Android实现京东首页效果

    Android实现京东首页效果

    这篇文章主要为大家详细介绍了Android实现京东首页效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • Android 使用Intent传递数据的实现思路与代码

    Android 使用Intent传递数据的实现思路与代码

    Intent是Android中一个非常重要的概念,跟这个词的本意(意图,目的)一样,这个类在Android中的作用就是要调用某个组建去做某一件事,接下来详细介绍,感兴趣的朋友可以参考下
    2013-01-01
  • Jetpack Compose入门基础全面精讲

    Jetpack Compose入门基础全面精讲

    开始布局部分。这部分我个人感觉没有必要每个组件、属性都详细说到,否则篇幅会很长。建立起Compose中的组件与 Android Views的一个对应关系就够了。具体还是需要在实际的使用中去熟悉
    2022-10-10
  • Android编程图片加载类ImageLoader定义与用法实例分析

    Android编程图片加载类ImageLoader定义与用法实例分析

    这篇文章主要介绍了Android编程图片加载类ImageLoader定义与用法,结合实例形式分析了Android图片加载类ImageLoader的功能、定义、使用方法及相关操作注意事项,代码中备有较为详尽的注释便于理解,需要的朋友可以参考下
    2017-12-12

最新评论