我很习惯使用RX来处理并发,但在我目前的工作中,我们混合了AsyncTask、Executor+ handle、Thread和一些LiveData。现在我们正在考虑转向使用Kotlin协程(实际上已经开始在代码库的某些地方使用它)。
因此,我需要开始考虑协程,理想的情况是利用我现有的并发工具知识来加速这个过程。
我试着使用Google codelab来帮助他们,虽然它给了我一些理解,但也提出了很多未回答的问题,所以我试着通过写一些代码,调试和查看日志输出来弄脏我的手。
据我所知,协程由两个主要的构建块组成;挂起函数是您工作的地方,协程上下文是您执行挂起函数的地方,这样您就可以控制协程将在哪个调度程序上运行。
这里我有一些下面的代码,它的行为与我预期的一样。我已经使用Dispatchers.Main设置了一个协程上下文。因此,不出所料,当我启动协程getResources时,由于Thread.sleep(5000)的原因,它会阻塞UI线程5秒钟
private const val TAG = "Coroutines"
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
log("onCreate", "launching coroutine")
launch {
val resource = getResource()
log("onCreate", "resource fetched: $resource")
findViewById<TextView>(R.id.textView).text = resource.toString()
}
log("onCreate", "coroutine launched")
}
private suspend fun getResource() : Int {
log("getResource", "about to sleep for 5000ms")
Thread.sleep(5000)
log("getResource", "finished fetching resource")
return 1
}
private fun log(methodName: String, toLog: String) {
Log.d(TAG,"$methodName: $toLog: ${Thread.currentThread().name}")
}
}当我运行这段代码时,我看到以下日志:
2020-05-28 11:42:44.364 9819-9819/? D/Coroutines: onCreate: launching coroutine: main
2020-05-28 11:42:44.376 9819-9819/? D/Coroutines: onCreate: coroutine launched: main
2020-05-28 11:42:44.469 9819-9819/? D/Coroutines: getResource: about to sleep for 5000ms: main
2020-05-28 11:42:49.471 9819-9819/com.example.coroutines D/Coroutines: getResource: finished fetching resource: main
2020-05-28 11:42:49.472 9819-9819/com.example.coroutines D/Coroutines: onCreate: resource fetched: 1: main如您所见,所有日志都来自主线程,并且在Thread.sleep(5000)之前和之后的日志之间有5秒的间隔。在这5秒的间隔内,UI线程被阻塞,我可以通过查看仿真器来确认这一点;它不会呈现任何UI,因为onCreate被阻塞了。
现在,如果我将getResources函数更新为使用suspend fun delay(5000)而不是使用Thread.sleep(5000),如下所示:
private suspend fun getResource() : Int {
log("getResource", "about to sleep for 5000ms")
delay(5000)
log("getResource", "finished fetching resource")
return 1
}然后我最终看到的东西让我感到困惑。我知道delay和Thread.sleep不一样,但因为我是在Dispatchers.Main支持的协程环境中运行它,所以我希望看到与使用Thread.sleep相同的结果。
相反,我看到的是UI线程在5秒延迟发生时没有阻塞,日志看起来像这样:
2020-05-28 11:54:19.099 10038-10038/com.example.coroutines D/Coroutines: onCreate: launching coroutine: main
2020-05-28 11:54:19.111 10038-10038/com.example.coroutines D/Coroutines: onCreate: coroutine launched: main
2020-05-28 11:54:19.152 10038-10038/com.example.coroutines D/Coroutines: getResource: about to sleep for 5000ms: main
2020-05-28 11:54:24.167 10038-10038/com.example.coroutines D/Coroutines: getResource: finished fetching resource: main
2020-05-28 11:54:24.168 10038-10038/com.example.coroutines D/Coroutines: onCreate: resource fetched: 1: main我可以看到UI线程在这种情况下没有被阻塞,因为在延迟发生的同时UI呈现,然后文本视图在5秒后更新。
所以,我的问题是,在这种情况下,延迟如何不阻塞UI线程(即使我的暂停函数中的日志仍然表明该函数正在主线程上运行...)
发布于 2020-05-28 21:23:18
可以将挂起函数看作是使用接受回调的函数的一种方式,但不需要将该回调传递给它。相反,回调代码就是suspend函数调用下的所有内容。
这段代码:
lifecycleScope.launch {
myTextView.text = "Starting"
delay(1000L)
myTextView.text = "Processing"
delay(2000L)
myTextView.text = "Done"
}有点类似于:
myTextView.text = "Starting"
handler.postDelayed(1000L) {
myTextView.text = "Processing"
handler.postDelayed(2000L) {
myTextView.text = "Done"
}
}决不能期望挂起函数会阻塞。如果它们这样做了,那么它们的组成就不正确了。挂起函数中的任何阻塞代码都应该包装在它的背景中,比如withContext或suspendCancellableCoroutine (这是较低级别的代码,因为它直接与协程延续一起工作)。
如果您尝试像这样编写一个挂起函数:
suspend fun myDelay(length: Long) {
Thread.sleep(length)
}你会收到一个“不适当的阻塞方法调用”的编译器警告。如果您将其推送到后台调度程序,则不会收到警告:
suspend fun myDelay(length: Long) = withContext(Dispatchers.IO) {
Thread.sleep(length)
}如果您尝试将它发送到Dispatchers.Main,您将再次收到警告,因为编译器认为主线程上的任何阻塞代码都是不正确的。
这应该会给你一个暂停函数应该如何操作的想法,但请记住,编译器并不总是将方法调用识别为阻塞。
发布于 2020-05-28 21:05:57
将你现有的直觉与协程世界联系起来的最好方法是进行这种心理映射:在传统的世界中,操作系统将线程调度到CPU核心(根据需要抢先挂起它们),而调度器将协程调度到线程。协程不能被先发制人地挂起,这就是协程并发的协作性质。
考虑到这一点:
因为我是在
Dispatchers.Main支持的协程上下文中运行它,所以我希望看到与使用Thread.sleep相同的结果。
delay(delayTime)只是暂停协程,并在稍后安排它的恢复delayTime。因此,你应该期望看到一个与Thread.sleep截然不同的结果,它从不挂起协程并一直占用它的线程,这种情况与Thread.sleep()不允许CPU核心运行其他东西,但会忙-等待的情况相当。
https://stackoverflow.com/questions/62064123
复制相似问题