赞
踩
同步(Sync)
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回,这样就不能继续执行后续操作。
异步(Async)
所谓同步,就是调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,通过状态、通知和回调来通知调用者。
为什么使用异步——提升效率
异步一般都能够提升效率 这也是我们使用异步的主要原因
异步带来的问题——回调嵌套
异步调用有依赖关系时 每增加一个调用 回调的嵌套层级也相应增加
比如 3个接口调用串行,回调嵌套保证了执行顺序:
请求接口1成功–>请求接口2成功–>请求接口3成功–>更新UI
//请求 接口1 api1 ( cllback( //请求接口2 api2( callback( //请求接口3 api3( callback( //更新UI ) ) ) ) ) })
再比如3个接口并行,回调逻辑保证了执行顺序
请求结果需要汇总后更新UI
//并行 汇总 api1( callback( if(api2已完成 && api3已完成){ updateUI() } ) ) api2(callback( if(api1已完成 && api3已完成){ updateUI() } ) ) api3(callback( if(api1已完成 && api2已完成){ updateUI() } ) )
if(api2已完成 && api3已完成)
if(api1已完成 && api3已完成)
if(api1已完成 && api2已完成)
嗯 这~ 谁用谁知道
那怎么解决异步调用的回调嵌套呢
说到异步 大名鼎鼎的RxJava是怎么做的
RxJava的链式调用
//1.串行 flatmap api1() .flatMap { api2() }.flatMap { api3() }.subscribe { updateUI() } //2.串行 cocat Observable.concat(api1(), api2(), api3()) .subscribe{ updateUI() } //3.并行 merge Observable.merge(api1(), api2(), api3()) .subscribe{ updateUI() } //4.并行 zip Observable.zip(api1(), api2(),BiFunction.apply{ value1 + value2 }) .subscribe{ updateUI() }
嗯 RxJava的链式调用能在调用逻辑复杂时 依然保持调用形式上的简单
那么问题来了 既然有RxJava 还要什么Kotlin协程?(要啥自行车)
Another way!
协程实现串行3个接口访问 launch{} 按前后顺序书写即可
//串行
launch{
value1 = api1()
vale2 = api2(vlaue1)
value3 = api3(value2)
updateUI(value3)
}
suspend fun api1():Value1{...}
suspend fun api2():Value2{...}
suspend fun api3():Value3{...}
fun updateUI(){...}
接下来看一下并行数据汇总 async{} await()
launch{
value1 = async{ api1() }
value2 = async{ api2() }
value3 = async{ api3() }
//这里会等待value1 value2 value3都请求完成
updateUI(value1.await(), value2.await(), value3.await())
}
suspend fun api1():Value1{...}
suspend fun api2():Value2{...}
suspend fun api3():Value3{...}
fun updateUI(){...}
什么是进程、线程、协程?
协程是一种编程概念 就像进程、线程一样。 Go、Python等编程语言都有协程的概念 。
进程是应用程序的启动实例,进程拥有代码和打开的文件资源、数据资源、独立的内存空间。进程的调度者是系统。
线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。线程的调度者是系统。
协程是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。协程的调度者是用户。
进程 线程 协程的关系图:
Kotlin中的协程“
Google Android 上的 Kotlin 协程
协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。
协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码 (用同步的方式编写异步代码 真香)。
协程是我们在 Android 上进行异步编程的推荐解决方案。
轻量:
单个线程上运行多个协程,因为协程支持暂停,不会使正在运行协程的线程阻塞。暂停比阻塞节省内存,且支持多个并行操作。
内存泄露更少:
使用结构化并发机制在一个作用域内执行多个操作。
内置取消支持:
取消功能会自动通过正在运行的协程层次结构传播。
Jetpack 集成:
许多 Jetpack 库都包含提供全面协程支持的扩展。
关于Kotlin的协程 这里引出几个问题
1.Continuation
从方法调用方式说起
Java内存分区图 这里只关注 虚拟机栈
Java虚拟机栈是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:
每个方法在执行的同时都会创建一个栈帧(frame)用于存储局部变量、操作数、操作数栈、动态链接、方法出口(return)等信息。
每一个方法的调用过程直至执行完成的过成,就对应着一个栈帧(frame)在虚拟机栈中入栈到出栈的过程。
可见Java方法是通过Java方法栈实现调用。
Continuation是另一种函数调用方式。
上下文信息保存在continuation record中 组成树/图的结构。
调用函数就等于给当前节点生成一个子节点,然后把系统寄存器移动到这个子节点。函数的退出等于从当前节点退回到父节点。
节点的删除是由gc来管理
这样的调用方式和堆栈方式相比的好处在哪里呢?
好处就是,它可以让你从任意一个节点跳到另一个节点。而不必遵循堆栈方式的一层一层的return方式。
比如说,在当前的函数内,你只要有一个其它函数的节点信息,完全可以选择return到那个函数,而不是返回到自己的调用者。
你也可以在一个函数的任何位置储存自己的上下文信息,然后,在以后某个适当的时刻,从其它的任何一个函数里面返回到自己现在的位置。(重点)
Continuation思想的一个重要应用场景就是Continuation-passing style(CPS) 续体风格
2.CPS变换
CPS的基本思想是:当需要返回某些数据的时候,不是直接把它当作函数的返回值,而是接受一个叫做continuation的参数。这个参数就是一个call-back函数, 它接受这个数据,并做需要做的事情。
//普通函数
fun foo(x)
{
return x;
}
//CPS风格函数
fun foo(x, continuation)
{
continuation(x);
//注意:调用continuation(x)相当于“return”,之后的语句都不会被执行了。
assert(false, "should not be here");
}
CPS的使用特点:
so,我知道了这些 有什么用?客官请注意 异步编程 (点题了啊),在说CPS在异步编程的应用之前 我们先来了解下 续体拦截器
我们知道
如果需要在跨线程场景下发起Continuation调用 需要指定Continuation运行的线程 这个要用到续体拦截器(ContinuationInterceptor)
3.suspend 关键字
suspend 挂起
用于修饰函数 被suspend修饰的函数称之为挂起函数
挂起函数只能在另一个挂起函数或者协程中被调用
挂起函数不仅支持call和return 还“支持” suspend和resume
Kotlin 中被 suspend 修饰符修饰的函数在编译期间会被编译器做特殊处理。而这个特殊处理的第一道工序就是:CPS(续体传递风格)变换,它会改变挂起函数的函数签名。
举个栗子:
挂起函数 getIpAddress() 的函数签名如下所示:
suspend fun getIpAddress(ip: String): String {
return withContext(Dispatchers.IO) {
println("getIpAddress ${Thread.currentThread().name}")
val json = URL(IPAddress.IP.replace("IP", ip)).readText()
val resultType = object : TypeToken<BaseBean<IPBean>>() {}.type
val bean = Gson().fromJson<BaseBean<IPBean>>(json, resultType)
val city = bean.result.City
println("city-->$city")
city
}
}
在编译期发生 CPS 变换之后:
public final Object getIpAddress(String paramString, Continuation paramContinuation) {
CoroutineContext coroutineContext = (CoroutineContext)Dispatchers.getIO();
ApiDataCoroutine$getIpAddress$2 apiDataCoroutine$getIpAddress$2 = new ApiDataCoroutine$getIpAddress$2();
this(paramString, null);
apiDataCoroutine$getIpAddress$2 = apiDataCoroutine$getIpAddress$2;
return BuildersKt.withContext(coroutineContext, apiDataCoroutine$getIpAddress$2, paramContinuation);
}
Kotlin中的Continuation声明类型为接口
interface Continuation<in T> {
//协程上下文
val context: CoroutineContext
//续体恢复时调用
fun resumeWith(result: Result<T>)
}
续体Continuation,它包装了协程在挂起之后应该继续执行的代码
协程可以在挂起函数的地方挂起(Continuation续体暂停),挂起结束后恢复(Continuation 续体恢复)
4.状态机
举个栗子
接口串行
GlobalScope.launch(Dispatchers.Main) { println("协程Start ${Thread.currentThread().name}") val city = getIpAddress(ip) //IO线程获取IP所属地 updateCityUI(cityTv, city)//主线程更新UI val weather = getWeather(city)//IO线程获取IP所属地的天气 需要参数city updateWeatherUI(weatherTv, weather)//主线程更新UI println("协程End ${Thread.currentThread().name}") } suspend fun getIpAddress(ip: String): String { return withContext(Dispatchers.IO) { val json = URL(IPAddress.IP.replace("IP", ip)).readText() ...... val city = bean.result.City city } } suspend fun getWeather(city: String): String { return withContext(Dispatchers.IO) { Thread.sleep(2_000) val json = URL(IPAddress.WEATHER.replace("CITY", city.substring(0, 2))).readText() ...... val weather = bean.result.future[0].weather weather } }
以上代码反编译后
public final Object invokeSuspend(Object paramObject) { Object object1 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); int i = this.label; Object object2 = "Thread.currentThread()"; byte b1 = 4; byte b2 = 3; byte b3 = 2; byte b4 = 1; switch (this.label) { case 0: { ResultKt.throwOnFailure(paramObject); CoroutineScope coroutineScope = this.p$; StringBuilder stringBuilder2 = new StringBuilder(); this(); stringBuilder2.append(""); Thread thread = Thread.currentThread(); Intrinsics.checkExpressionValueIsNotNull(thread, (String) object2); String str3 = thread.getName(); stringBuilder2.append(str3); String str2 = stringBuilder2.toString(); System.out.println(str2); ApiDataCoroutine apiDataCoroutine2 = ApiDataCoroutine.INSTANCE; str3 = this.$ip; this.L$0 = coroutineScope; this.label = b4; Object object4 = apiDataCoroutine2.getIpAddress(str3, (Continuation) this); if (object4 == object1) return object1; object4 = object4; StringBuilder stringBuilder1 = new StringBuilder(); this(); stringBuilder1.append("city-->"); stringBuilder1.append((String) object4); String str1 = stringBuilder1.toString(); System.out.println(str1); ApiDataCoroutine apiDataCoroutine1 = ApiDataCoroutine.INSTANCE; TextView textView = this.$cityTv; this.L$0 = coroutineScope; this.L$1 = object4; this.label = b3; Object object3 = apiDataCoroutine1.updateCityUI(textView, (String) object4, (Continuation) this); break; } case 1: { i = 0; Object object5 = this.L$0; Object object3 = object5; object3 = object5; ResultKt.throwOnFailure(paramObject); object5 = paramObject; object5 = object5; StringBuilder stringBuilder = new StringBuilder(); this(); stringBuilder.append("city-->"); stringBuilder.append((String) object5); String str = stringBuilder.toString(); System.out.println(str); ApiDataCoroutine apiDataCoroutine = ApiDataCoroutine.INSTANCE; TextView textView = this.$cityTv; this.L$0 = object3; this.L$1 = object5; this.label = b3; Object object4 = apiDataCoroutine.updateCityUI(textView, (String) object5, (Continuation) this); break; } case 2: { i = 0; b3 = 0; Object object6 = this.L$1; Object object4 = object6; object4 = object6; object6 = this.L$0; Object object3 = object6; object3 = object6; ResultKt.throwOnFailure(paramObject); object6 = ApiDataCoroutine.INSTANCE; this.L$0 = object3; this.L$1 = object4; this.label = b2; Object object5 = object6.getWeather((String) object4, (Continuation) this); break; } case 3: { i = 0; b2 = 0; Object object4 = this.L$1; Object object5 = object4; object5 = object4; object4 = this.L$0; Object object3 = object4; object3 = object4; ResultKt.throwOnFailure(paramObject); object4 = object5; object5 = paramObject; object5 = object5; StringBuilder stringBuilder = new StringBuilder(); this(); stringBuilder.append(""); thread = Thread.currentThread(); Intrinsics.checkExpressionValueIsNotNull(thread, (String) object2); object2 = thread.getName(); stringBuilder.append((String) object2); object2 = stringBuilder.toString(); System.out.println(object2); object2 = ApiDataCoroutine.INSTANCE; TextView textView = this.$weatherTv; this.L$0 = object3; this.L$1 = object4; this.L$2 = object5; this.label = b1; object2 = object2.updateWeatherUI(textView, (String) object5, (Continuation) this); break; } case 4: i = 0; Object object3 = this.L$2; Object object4 = object3; object4 = object3; object3 = this.L$1; object2 = object3; object2 = object3; object3 = this.L$0; object1 = object3; object1 = object3; ResultKt.throwOnFailure(paramObject); break; default: object1 = new IllegalStateException(); super("call to 'resume' before 'invoke' with coroutine"); throw object1; break; } return Unit.INSTANCE; }
Kotlin的协程就是Kotlin提供的一套线程框架 ,本质上和Handler,AsyncTask,RxJava 是一致的。只不过协程封装的更友好,使用起来更方便。
协程可以用同步的方式写异步代码。
协程并没有比线程更高效、更轻量级。
怎样理解同步异步和阻塞非阻塞
同步(Synchronous)和异步(Asynchronous)
忘了RxJava吧——你需要的是拥抱Kotlin协程
Kotlin 协程真的比 Java 线程更高效吗
Kotlin Coroutines Offical Doc
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。