抛砖引玉
如果目前有三个方法,A任务依赖B任务完成,B任务依赖C任务完成,那么如何设计代码呢?
class API {
fun doFirstTask(callback: (String) -> Unit) {
Log.i("test", "Doing first task...")
//省略一些代码,例如切线程执行耗时操作
callback("Result of first task")
}
fun doSecondTask(input: String, callback: (Int) -> Unit) {
Log.i("test", "Doing second task with input: $input")
//省略一些代码,例如切线程执行耗时操作
callback(42)
}
fun doThirdTask(input: Int, callback: (Boolean) -> Unit) {
Log.i("test", "Doing third task with input: $input")
//省略一些代码,例如切线程执行耗时操作
callback(true)
}
}
假设我们使用的是Java,那么估计会有类似于如下的代码:
public void test() {
API api = new API();
api.doFirstTask(new Function1<String, Unit>() {
@Override
public Unit invoke(String s) {
api.doSecondTask(s, new Function1<Integer, Unit>() {
@Override
public Unit invoke(Integer integer) {
api.doThirdTask(integer, new Function1<Boolean, Unit>() {
@Override
public Unit invoke(Boolean aBoolean) {
Logger.i("test", aBoolean.toString());
return null;
}
});
return null;
}
});
return null;
}
});
}
如果使用kotlin,那么代码将进一步简化:
val api = API()
api.doFirstTask { s ->
api.doSecondTask(s) { it1 ->
api.doThirdTask(it1) {
Log.i(TAG, it.toString())
}
}
}
那么如果我们使用协程,又该如何呢?
协程亮相
首先,我们之前写的API方法中不再需要维护callback,怎么样,是不是很爽,然后就是线程切换的操作也不再需要我们来写了。那么如何实现呢?
先要改造一下之前的API方法,将里面callback直接以返回值的形式表示,然后在函数前面加一个suspend,可以暂时理解这个变量为一个协程函数的关键字,后续会展开讲解。另外,为了方便模拟耗时操作,用delay函数来做延迟,表示延迟xxx时间后再继续执行后面代码。
class APIkotlin {
suspend fun doFirstTask(): String {
withContext(Dispatchers.IO) {
Logg.i("test", "Doing first task...")
//某线程耗时操作
}
return "First task done"
}
suspend fun doSecondTask(input: String): Int {
withContext(Dispatchers.IO) {
Logger.i("test", "start doSecondTask Doing second task with input: $input")
delay(3000L)
Log.i("test", "end doSecondTask Doing second task with input: $input")
}
//某线程耗时操作
return 42
}
suspend fun doThirdTask(input: Int): Boolean {
withContext(Dispatchers.IO) {
//某线程耗时操作
Logger.i("test", "start Doing third task with input: $input")
delay(2000L)
Log.i("test", "end Doing third task with input: $input")
}
return true
}
}
最后,我们的三个存在依赖关系且存在线程切换的方法就可以写成:
val josb = CoroutineScope(Dispatchers.IO).launch {
Log.i(TAG, "CoroutineScope start")
val api = APIkotlin()
val result1 = api.doFirstTask()
val result2 = api.doSecondTask(result1)
val result3 = api.doThirdTask(result2)
Log.i(TAG, "$result3")
}
介绍到这里,是不是对协程还是一头雾水,其实简单概述协程,它就是一种用于处理异步任务的编程技术(划重点,就是一种花花手段而已,不要想的很高深)。它可以让你以一种顺序、简洁和直观的方式处理异步代码,而无需使用传统的回调函数或者繁琐的线程管理。
想象一下,你有一些需要花费时间来执行的任务,比如从网络上下载数据或者进行长时间的计算。在传统的方式中,你可能需要使用回调函数或者创建新的线程来处理这些任务。但是,这样的代码往往会变得复杂难以维护。
而 Kotlin 协程提供了一种更好的解决方案。它允许你将异步任务定义为挂起函数(没错,就是这个关键字suspend),这些函数在执行时可以暂停并等待结果,而不会阻塞线程。(这时是不是有疑问,为啥不会阻塞线程?)
简单举个例子,还是使用到上述写到的APIkotlin函数:
Log.i(TAG, "start main")
val josb = CoroutineScope(Dispatchers.IO).launch {
Log.i(TAG, "CoroutineScope start")
val api = APIkotlin()
val result1 = api.doFirstTask()
val result2 = api.doSecondTask(result1)
val result3 = api.doThirdTask(result2)
Log.i(TAG, "$result3")
}
Thread.sleep(2000)
Log.i(TAG, "end main")
具体输出为:
2024-02-08 15:28:57.526 18265-18265 I start main
2024-02-08 15:28:57.527 18265-19964 I CoroutineScope start
2024-02-08 15:28:57.528 18265-19964 I Doing first task...
2024-02-08 15:28:57.528 18265-19964 I start doSecondTask Doing second task with input: First task done
2024-02-08 15:28:59.527 18265-18265 I end main
2024-02-08 15:29:00.530 18265-19964 I end doSecondTask Doing second task with input: First task done
2024-02-08 15:29:00.530 18265-19964 I start Doing third task with input: 42
2024-02-08 15:29:02.532 18265-19964 I end Doing third task with input: 42
2024-02-08 15:29:02.532 18265-19964 I true
可以细细品一下输出哈,可以看出,在主线程中阻塞的时候,协程还在继续执行,这是因为协程本身被放置到了IO线程中。至于kotlin协程很多文章中说的不会阻塞线程,其实就是因为你主要流程在UI线程中运行,然后你写了一个需要运行在IO线程的协程
那么如果我的协程也是放在主线程执行的呢?修改上述调度器为:Dispatchers.Main,重新运行,输出为:
2024-02-08 15:44:39.203 26530-26530 I start main
2024-02-08 15:44:41.211 26530-26530 I end main
2024-02-08 15:44:41.243 26530-26530 I CoroutineScope start for job1
2024-02-08 15:44:41.256 26530-28700 I Doing first task...
2024-02-08 15:44:41.275 26530-28700 I start doSecondTask Doing second task with input: First task done
2024-02-08 15:44:44.281 26530-28700 I end doSecondTask Doing second task with input: First task done
2024-02-08 15:44:44.288 26530-28700 I start Doing third task with input: 42
2024-02-08 15:44:46.292 26530-28700 I end Doing third task with input: 42
2024-02-08 15:44:46.296 26530-26530 I true
2024-02-08 15:44:46.297 26530-26530 I CoroutineScope end for job1
是不是清晰很多,在运行到协程的时候,并不会阻塞主线程,因此会继续往下执行,进而被Thread.sleep阻塞了2s,并且因为协程也要运行在主线程中,因此等待cpu释放后,继续执行协程。
暂时就介绍这么多啦,具体的细节很多,一个关键词解释起来也很耗费篇幅,对于想要入门使用的,可以先从本文中初识大体,后续会继续推出关于kotlin协程的文章。