默认顺序调用
假设我们在不同的地方定义了两个进行某种调用远程服务或者进行计算的挂起函数。我们只假设它们都是有用的,但是实际上它们在这个示例中只是为了该目的而延迟了一秒钟:
private suspend fun jobOne(): Int {
delay(2500)
return 1
}
private suspend fun jobTwo(): Int {
delay(1500)
return 2
}
如果需要按 顺序 调用它们,我们接下来会做什么——首先调用 doSomethingUsefulOne
接下来 调用 doSomethingUsefulTwo
,并且计算它们结果的和吗? 实际上,如果我们要根据第一个函数的结果来决定是否我们需要调用第二个函数或者决定如何调用它时,我们就会这样做。
我们使用普通的顺序来进行调用,因为这些代码是运行在协程中的,只要像常规的代码一样 顺序 都是默认的。下面的示例展示了测量执行两个挂起函数所需要的总时间:
串行写法一:
override fun onStart() {
super.onStart()
println("onStart")
GlobalScope.launch(Dispatchers.Main) {
val time = measureTimeMillis {
val one2 = jobOne()
val two2 = jobTwo()
println("job The answer is ${one2 + two2}")
}
println("job 执行耗时:${time}")
}
println("job ----------------")
/*应为上面的协程代码并不会阻塞掉线程,所以我们这里让线程睡4秒,保证线程的存活,在实际的Android开发中无需这么做*/
Thread.sleep(4000)
}
串行写法二:
override fun onStart() {
super.onStart()
println("onStart")
GlobalScope.launch(Dispatchers.Main) {
val time = measureTimeMillis {
val jobOne = withContext(Dispatchers.IO) {
jobOne()
}
println("job one:${jobOne}")
val jobTwo = withContext(Dispatchers.IO) {
jobTwo()
}
println("job two:${jobTwo}")
}
println("job 执行耗时:${time}")
}
println("job ----------------")
/*应为上面的协程代码并不会阻塞掉线程,所以我们这里让线程睡4秒,保证线程的存活,在实际的Android开发中无需这么做*/
Thread.sleep(4000)
}
它的打印输出如下:
串行写法三:
override fun onStart() {
super.onStart()
println("onStart")
GlobalScope.launch(Dispatchers.Main) {
val time = measureTimeMillis {
val firstJob = async { jobOne() }
val secondJob = async {
firstJob.await()//如果第二个Job的开始需要等第一个job的结果
jobTwo()
}
println("firstJob: ${firstJob.await()}, secondJob: ${secondJob.await()}")
}
println("job 执行耗时:${time}")
}
println("job ----------------")
/*应为上面的协程代码并不会阻塞掉线程,所以我们这里让线程睡4秒,保证线程的存活,在实际的Android开发中无需这么做*/
Thread.sleep(5000)
}
使用 async 并发
如果 doSomethingUsefulOne
与 doSomethingUsefulTwo
之间没有依赖,并且我们想更快的得到结果,让它们进行 并发 吗?这就是 async 可以帮助我们的地方。
在概念上,async 就类似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 launch
返回一个 Job 并且不附带任何结果值,而 async
返回一个 Deferred —— 一个轻量级的非阻塞 future, 这代表了一个将会在稍后提供结果的 promise。你可以使用 .await()
在一个延期的值上得到它的最终结果, 但是 Deferred
也是一个 Job
,所以如果需要的话,你可以取消它。
override fun onStart() {
super.onStart()
println("onStart")
GlobalScope.launch(Dispatchers.Main) {
val time = measureTimeMillis {
val one = async { jobOne() }
val two = async { jobTwo() }
println("job The answer is ${one.await() + two.await()}")
}
println("job 执行耗时:${time}")
}
println("job ----------------")
/*应为上面的协程代码并不会阻塞掉线程,所以我们这里让线程睡4秒,保证线程的存活,在实际的Android开发中无需这么做*/
Thread.sleep(4000)
}
它的打印输出如下:
构建协程的函数有两个,一个是launch,一个是async。其中,async是带返回值的launch。这点从函数名也能看出来,launch 更多是用来发起一个无需结果的耗时任务(如批量文件删除、创建),这个工作不需要返回结果。async 函数则是更进一步,用于异步执行耗时任务,并且需要返回值(如网络请求、数据库读写、文件读写),在执行完毕通过 await() 函数获取返回值。
launch与async同时使用:
用withContext可以解决用多个嵌套launch引起的回调地狱问题:
我们可以把withContext的内容放到一个单独的方法中,但是这个方法必须用suspend修饰: